/* kitchen sink for OsmoHNBGW implementation */ /* (C) 2015,2024 by Harald Welte * (C) 2016-2023 by sysmocom s.f.m.c. GmbH * 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 . * */ #include #include #include #include #include #include #include #include #include "config.h" #if ENABLE_PFCP #include #endif #include #include #include #include #include #include struct hnbgw *g_hnbgw = NULL; const struct value_string ranap_domain_names[] = { { DOMAIN_CS, "CS" }, { DOMAIN_PS, "PS" }, {} }; /* update the active RAB duration rate_ctr for given HNB */ static void hnb_store_rab_durations(struct hnb_context *hnb) { struct hnbgw_context_map *map; struct timespec now; uint64_t elapsed_cs_rab_ms = 0; osmo_clock_gettime(CLOCK_MONOTONIC, &now); /* iterate over all context_maps (subscribers) */ llist_for_each_entry(map, &hnb->map_list, hnb_list) { /* skip any PS maps, we care about CS RABs only here */ if (map->is_ps) continue; elapsed_cs_rab_ms += mgw_fsm_get_elapsed_ms(map, &now); } /* Export to rate countes. */ rate_ctr_add(HNBP_CTR(hnb->persistent, HNB_CTR_RAB_ACTIVE_MILLISECONDS_TOTAL), elapsed_cs_rab_ms); } static void hnbgw_store_hnb_rab_durations(void *data) { struct hnb_context *hnb; llist_for_each_entry(hnb, &g_hnbgw->hnb_list, list) { if (!hnb->persistent) continue; hnb_store_rab_durations(hnb); } /* Keep this timer ticking */ osmo_timer_schedule(&g_hnbgw->hnb_store_rab_durations_timer, HNB_STORE_RAB_DURATIONS_INTERVAL, 0); } /*********************************************************************** * UE Context ***********************************************************************/ struct ue_context *ue_context_by_id(uint32_t id) { struct ue_context *ue; llist_for_each_entry(ue, &g_hnbgw->ue_list, list) { if (ue->context_id == id) return ue; } return NULL; } struct ue_context *ue_context_by_imsi(const char *imsi) { struct ue_context *ue; llist_for_each_entry(ue, &g_hnbgw->ue_list, list) { if (!strcmp(ue->imsi, imsi)) return ue; } return NULL; } struct ue_context *ue_context_by_tmsi(uint32_t tmsi) { struct ue_context *ue; llist_for_each_entry(ue, &g_hnbgw->ue_list, list) { if (ue->tmsi == tmsi) return ue; } return NULL; } static void ue_context_free_by_hnb(const struct hnb_context *hnb) { struct ue_context *ue, *tmp; llist_for_each_entry_safe(ue, tmp, &g_hnbgw->ue_list, list) { if (ue->hnb == hnb) ue_context_free(ue); } } static uint32_t get_next_ue_ctx_id(void) { uint32_t id; do { id = g_hnbgw->next_ue_ctx_id++; } while (ue_context_by_id(id)); return id; } struct ue_context *ue_context_alloc(struct hnb_context *hnb, const char *imsi, uint32_t tmsi) { struct ue_context *ue; ue = talloc_zero(g_hnbgw, struct ue_context); if (!ue) return NULL; ue->hnb = hnb; if (imsi) OSMO_STRLCPY_ARRAY(ue->imsi, imsi); else ue->imsi[0] = '\0'; ue->tmsi = tmsi; ue->context_id = get_next_ue_ctx_id(); llist_add_tail(&ue->list, &g_hnbgw->ue_list); LOGP(DHNBAP, LOGL_INFO, "created UE context: id 0x%x, imsi %s, tmsi 0x%x\n", ue->context_id, imsi? imsi : "-", tmsi); return ue; } void ue_context_free(struct ue_context *ue) { llist_del(&ue->list); talloc_free(ue); } /*********************************************************************** * HNB Context ***********************************************************************/ /* look-up HNB context by id. Used from CTRL */ static struct hnb_context *hnb_context_by_id(uint32_t cid) { struct hnb_context *hnb; llist_for_each_entry(hnb, &g_hnbgw->hnb_list, list) { if (hnb->id.cid == cid) return hnb; } return NULL; } /* look-up HNB context by identity_info. Used from VTY */ struct hnb_context *hnb_context_by_identity_info(const char *identity_info) { struct hnb_context *hnb; llist_for_each_entry(hnb, &g_hnbgw->hnb_list, list) { if (strcmp(identity_info, hnb->identity_info) == 0) return hnb; } return NULL; } static int hnb_read_cb(struct osmo_stream_srv *conn); static int hnb_closed_cb(struct osmo_stream_srv *conn); static struct hnb_context *hnb_context_alloc(struct osmo_stream_srv_link *link, int new_fd) { struct hnb_context *ctx; ctx = talloc_zero(g_hnbgw, struct hnb_context); if (!ctx) return NULL; INIT_LLIST_HEAD(&ctx->map_list); ctx->conn = osmo_stream_srv_create(g_hnbgw, link, new_fd, hnb_read_cb, hnb_closed_cb, ctx); if (!ctx->conn) { LOGP(DMAIN, LOGL_INFO, "error while creating connection\n"); talloc_free(ctx); return NULL; } llist_add_tail(&ctx->list, &g_hnbgw->hnb_list); return ctx; } const char *umts_cell_id_name(const struct umts_cell_id *ucid) { const char *fmtstr = "%03u-%02u-L%u-R%u-S%u-C%u"; if (g_hnbgw->config.plmn.mnc_3_digits) fmtstr = "%03u-%03u-L%u-R%u-S%u-C%u"; return talloc_asprintf(OTC_SELECT, fmtstr, ucid->mcc, ucid->mnc, ucid->lac, ucid->rac, ucid->sac, ucid->cid); } /* parse a string representation of an umts_cell_id into its decoded representation */ int umts_cell_id_from_str(struct umts_cell_id *ucid, const char *instr) { int rc = sscanf(instr, "%hu-%hu-L%hu-R%hu-S%hu-C%u", &ucid->mcc, &ucid->mnc, &ucid->lac, &ucid->rac, &ucid->sac, &ucid->cid); if (rc < 0) return -errno; if (rc != 6) return -EINVAL; if (ucid->mcc > 999) return -EINVAL; if (ucid->mnc > 999) return -EINVAL; if (ucid->lac == 0 || ucid->lac == 0xffff) return -EINVAL; /* CellIdentity in the ASN.1 syntax is a bit-string of 28 bits length */ if (ucid->cid >= (1 << 28)) return -EINVAL; return 0; } const char *hnb_context_name(struct hnb_context *ctx) { char *result; if (!ctx) return "NULL"; if (ctx->conn) { char hostbuf_r[INET6_ADDRSTRLEN]; char portbuf_r[6]; int fd = osmo_stream_srv_get_ofd(ctx->conn)->fd; /* get remote addr */ if (osmo_sock_get_ip_and_port(fd, hostbuf_r, sizeof(hostbuf_r), portbuf_r, sizeof(portbuf_r), false) == 0) result = talloc_asprintf(OTC_SELECT, "%s:%s", hostbuf_r, portbuf_r); else result = "?"; } else { result = "disconnected"; } if (g_hnbgw->config.log_prefix_hnb_id) result = talloc_asprintf(OTC_SELECT, "%s %s", result, ctx->identity_info); else result = talloc_asprintf(OTC_SELECT, "%s %s", result, umts_cell_id_name(&ctx->id)); return result; } void hnb_context_release_ue_state(struct hnb_context *ctx) { struct hnbgw_context_map *map, *map2; /* deactivate all context maps */ llist_for_each_entry_safe(map, map2, &ctx->map_list, hnb_list) { context_map_hnb_released(map); /* hnbgw_context_map will remove itself from lists when it is ready. */ } ue_context_free_by_hnb(ctx); } void hnb_context_release(struct hnb_context *ctx) { struct hnbgw_context_map *map; LOGHNB(ctx, DMAIN, LOGL_INFO, "Releasing HNB context\n"); if (ctx->persistent) { struct timespec tp; int rc; rc = osmo_clock_gettime(CLOCK_MONOTONIC, &tp); ctx->persistent->updowntime = (rc < 0) ? 0 : tp.tv_sec; } /* remove from the list of HNB contexts */ llist_del(&ctx->list); hnb_context_release_ue_state(ctx); if (ctx->conn) { /* we own a conn, we must free it: */ LOGHNB(ctx, DMAIN, LOGL_INFO, "Closing HNB SCTP connection %s\n", osmo_sock_get_name2(osmo_stream_srv_get_ofd(ctx->conn)->fd)); /* Avoid our closed_cb calling hnb_context_release() again: */ osmo_stream_srv_set_data(ctx->conn, NULL); osmo_stream_srv_destroy(ctx->conn); } /* else: we are called from closed_cb, so conn is being freed separately */ /* hnbgw_context_map are still listed in ctx->map_list, but we are freeing ctx. Remove all entries from the * list, but keep the hnbgw_context_map around for graceful release. They are also listed under * hnbgw_cnlink->map_list, and will remove themselves when ready. */ while ((map = llist_first_entry_or_null(&ctx->map_list, struct hnbgw_context_map, hnb_list))) { llist_del(&map->hnb_list); map->hnb_ctx = NULL; } /* remove back reference from hnb_persistent to context */ if (ctx->persistent) { hnb_nft_kpi_end(ctx->persistent); ctx->persistent->ctx = NULL; } talloc_free(ctx); } /*********************************************************************** * HNB Persistent Data ***********************************************************************/ const struct rate_ctr_desc hnb_ctr_description[] = { [HNB_CTR_IUH_ESTABLISHED] = { "iuh:established", "Number of times Iuh link was established" }, [HNB_CTR_RANAP_PS_ERR_IND_UL] = { "ranap:ps:error_ind:ul", "Received ERROR Indications in Uplink (PS Domain)" }, [HNB_CTR_RANAP_CS_ERR_IND_UL] = { "ranap:cs:error_ind:ul", "Received ERROR Indications in Uplink (PS Domain)" }, [HNB_CTR_RANAP_PS_RESET_REQ_UL] = { "ranap:ps:reset_req:ul", "Received RESET Requests in Uplink (PS Domain)" }, [HNB_CTR_RANAP_CS_RESET_REQ_UL] = { "ranap:cs:reset_req:ul", "Received RESET Requests in Uplink (CS Domain)" }, [HNB_CTR_RANAP_PS_RAB_ACT_REQ] = { "ranap:ps:rab_act:req", "PS RAB Activations requested" }, [HNB_CTR_RANAP_CS_RAB_ACT_REQ] = { "ranap:cs:rab_act:req", "CS RAB Activations requested" }, [HNB_CTR_RANAP_PS_RAB_ACT_CNF] = { "ranap:ps:rab_act:cnf", "PS RAB Activations confirmed" }, [HNB_CTR_RANAP_CS_RAB_ACT_CNF] = { "ranap:cs:rab_act:cnf", "CS RAB Activations confirmed" }, [HNB_CTR_RANAP_PS_RAB_ACT_FAIL] = { "ranap:ps:rab_act:fail", "PS RAB Activations failed" }, [HNB_CTR_RANAP_CS_RAB_ACT_FAIL] = { "ranap:cs:rab_act:fail", "CS RAB Activations failed" }, [HNB_CTR_RANAP_PS_RAB_MOD_REQ] = { "ranap:ps:rab_mod:req", "PS RAB Modifications requested" }, [HNB_CTR_RANAP_CS_RAB_MOD_REQ] = { "ranap:cs:rab_mod:req", "CS RAB Modifications requested" }, [HNB_CTR_RANAP_PS_RAB_MOD_CNF] = { "ranap:ps:rab_mod:cnf", "PS RAB Modifications confirmed" }, [HNB_CTR_RANAP_CS_RAB_MOD_CNF] = { "ranap:cs:rab_mod:cnf", "CS RAB Modifications confirmed" }, [HNB_CTR_RANAP_PS_RAB_MOD_FAIL] = { "ranap:ps:rab_mod:fail", "PS RAB Modifications failed" }, [HNB_CTR_RANAP_CS_RAB_MOD_FAIL] = { "ranap:cs:rab_mod:fail", "CS RAB Modifications failed" }, [HNB_CTR_RANAP_PS_RAB_REL_REQ] = { "ranap:ps:rab_rel:req", "PS RAB Release requested" }, [HNB_CTR_RANAP_CS_RAB_REL_REQ] = { "ranap:cs:rab_rel:req", "CS RAB Release requested" }, [HNB_CTR_RANAP_PS_RAB_REL_CNF] = { "ranap:ps:rab_rel:cnf", "PS RAB Release confirmed" }, [HNB_CTR_RANAP_CS_RAB_REL_CNF] = { "ranap:cs:rab_rel:cnf", "CS RAB Release confirmed" }, [HNB_CTR_RANAP_PS_RAB_REL_FAIL] = { "ranap:ps:rab_rel:fail", "PS RAB Release failed" }, [HNB_CTR_RANAP_CS_RAB_REL_FAIL] = { "ranap:cs:rab_rel:fail", "CS RAB Release failed" }, [HNB_CTR_RANAP_PS_RAB_REL_IMPLICIT] = { "ranap:ps:rab_rel:implicit", "PS RAB Release implicit (during Iu Release)" }, [HNB_CTR_RANAP_CS_RAB_REL_IMPLICIT] = { "ranap:cs:rab_rel:implicit", "CS RAB Release implicit (during Iu Release)" }, [HNB_CTR_RUA_ERR_IND] = { "rua:error_ind", "Received RUA Error Indications" }, [HNB_CTR_RUA_PS_CONNECT_UL] = { "rua:ps:connect:ul", "Received RUA Connect requests (PS Domain)" }, [HNB_CTR_RUA_CS_CONNECT_UL] = { "rua:cs:connect:ul", "Received RUA Connect requests (CS Domain)" }, [HNB_CTR_RUA_PS_DISCONNECT_UL] = { "rua:ps:disconnect:ul", "Received RUA Disconnect requests in uplink (PS Domain)" }, [HNB_CTR_RUA_CS_DISCONNECT_UL] = { "rua:cs:disconnect:ul", "Received RUA Disconnect requests in uplink (CS Domain)" }, [HNB_CTR_RUA_PS_DISCONNECT_DL] = { "rua:ps:disconnect:dl", "Transmitted RUA Disconnect requests in downlink (PS Domain)" }, [HNB_CTR_RUA_CS_DISCONNECT_DL] = { "rua:cs:disconnect:dl", "Transmitted RUA Disconnect requests in downlink (CS Domain)" }, [HNB_CTR_RUA_PS_DT_UL] = { "rua:ps:direct_transfer:ul", "Received RUA DirectTransfer in uplink (PS Domain)" }, [HNB_CTR_RUA_CS_DT_UL] = { "rua:cs:direct_transfer:ul", "Received RUA DirectTransfer in uplink (CS Domain)" }, [HNB_CTR_RUA_PS_DT_DL] = { "rua:ps:direct_transfer:dl", "Transmitted RUA DirectTransfer in downlink (PS Domain)" }, [HNB_CTR_RUA_CS_DT_DL] = { "rua:cs:direct_transfer:dl", "Transmitted RUA DirectTransfer in downlink (CS Domain)" }, [HNB_CTR_RUA_UDT_UL] = { "rua:unit_data:ul", "Received RUA UnitData (UDT) in uplink" }, [HNB_CTR_RUA_UDT_DL] = { "rua:unit_data:dl", "Transmitted RUA UnitData (UDT) in downlink" }, [HNB_CTR_PS_PAGING_ATTEMPTED] = { "paging:ps:attempted", "Transmitted PS Paging requests" }, [HNB_CTR_CS_PAGING_ATTEMPTED] = { "paging:cs:attempted", "Transmitted CS Paging requests" }, [HNB_CTR_RAB_ACTIVE_MILLISECONDS_TOTAL] = { "rab:cs:active_milliseconds:total", "Cumulative number of milliseconds of CS RAB activity" }, [HNB_CTR_GTPU_PACKETS_UL] = { "gtpu:packets:ul", "Count of GTP-U packets received from the HNB", }, [HNB_CTR_GTPU_TOTAL_BYTES_UL] = { "gtpu:total_bytes:ul", "Count of total GTP-U bytes received from the HNB, including the GTP-U/UDP/IP headers", }, [HNB_CTR_GTPU_PACKETS_DL] = { "gtpu:packets:dl", "Count of GTP-U packets sent to the HNB", }, [HNB_CTR_GTPU_TOTAL_BYTES_DL] = { "gtpu:total_bytes:dl", "Count of total GTP-U bytes sent to the HNB, including the GTP-U/UDP/IP headers", }, }; const struct rate_ctr_group_desc hnb_ctrg_desc = { "hnb", "hNodeB", OSMO_STATS_CLASS_GLOBAL, ARRAY_SIZE(hnb_ctr_description), hnb_ctr_description, }; const struct osmo_stat_item_desc hnb_stat_desc[] = { [HNB_STAT_UPTIME_SECONDS] = { "uptime:seconds", "Seconds of uptime", "s", 60, 0 }, }; const struct osmo_stat_item_group_desc hnb_statg_desc = { .group_name_prefix = "hnb", .group_description = "hNodeB", .class_id = OSMO_STATS_CLASS_GLOBAL, .num_items = ARRAY_SIZE(hnb_stat_desc), .item_desc = hnb_stat_desc, }; struct hnb_persistent *hnb_persistent_alloc(const struct umts_cell_id *id) { struct hnb_persistent *hnbp = talloc_zero(g_hnbgw, struct hnb_persistent); if (!hnbp) return NULL; hnbp->id = *id; hnbp->id_str = talloc_strdup(hnbp, umts_cell_id_name(id)); hnbp->ctrs = rate_ctr_group_alloc(hnbp, &hnb_ctrg_desc, 0); if (!hnbp->ctrs) goto out_free; rate_ctr_group_set_name(hnbp->ctrs, hnbp->id_str); hnbp->statg = osmo_stat_item_group_alloc(hnbp, &hnb_statg_desc, 0); if (!hnbp->statg) goto out_free_ctrs; osmo_stat_item_group_set_name(hnbp->statg, hnbp->id_str); llist_add(&hnbp->list, &g_hnbgw->hnb_persistent_list); return hnbp; out_free_ctrs: rate_ctr_group_free(hnbp->ctrs); out_free: talloc_free(hnbp); return NULL; } struct hnb_persistent *hnb_persistent_find_by_id(const struct umts_cell_id *id) { struct hnb_persistent *hnbp; llist_for_each_entry(hnbp, &g_hnbgw->hnb_persistent_list, list) { if (umts_cell_id_equal(&hnbp->id, id)) return hnbp; } return NULL; } struct hnb_persistent *hnb_persistent_find_by_id_str(const char *id_str) { struct hnb_persistent *hnbp; llist_for_each_entry (hnbp, &g_hnbgw->hnb_persistent_list, list) { if (strcmp(hnbp->id_str, id_str)) continue; return hnbp; } return NULL; } /* Read the peer's remote IP address from the Iuh conn's fd, and set up GTP-U counters for that remote address. */ void hnb_persistent_update_addr(struct hnb_persistent *hnbp, int new_fd) { int rc; socklen_t socklen; struct osmo_sockaddr osa; struct osmo_sockaddr_str remote_str; socklen = sizeof(struct osmo_sockaddr); rc = getpeername(new_fd, &osa.u.sa, &socklen); if (!rc) rc = osmo_sockaddr_str_from_osa(&remote_str, &osa); if (rc < 0) { LOGP(DHNB, LOGL_ERROR, "cannot read remote hNodeB address from Iuh file descriptor\n"); return; } /* We got the remote address from the RUA link, and now we are blatantly assuming that the hNodeB has its GTP * endpoint on the same IP address, just with UDP port 2152 (the fixed GTP port as per 3GPP spec). */ remote_str.port = 2152; if (g_hnbgw->config.nft_kpi.enable) hnb_nft_kpi_start(hnbp, &remote_str); } void hnb_persistent_free(struct hnb_persistent *hnbp) { /* FIXME: check if in use? */ hnb_nft_kpi_end(hnbp); llist_del(&hnbp->list); talloc_free(hnbp); } /* return the amount of time the HNB is up (hnbp->ctx != NULL) or down (hnbp->ctx == NULL) */ static unsigned long long hnbp_get_updowntime(const struct hnb_persistent *hnbp) { struct timespec tp; if (!hnbp->updowntime) return 0; if (osmo_clock_gettime(CLOCK_MONOTONIC, &tp) != 0) return 0; return difftime(tp.tv_sec, hnbp->updowntime); } unsigned long long hnb_get_updowntime(const struct hnb_context *ctx) { if (!ctx->persistent) return 0; return hnbp_get_updowntime(ctx->persistent); } /* timer call-back: Update the HNB_STAT_UPTIME_SECONDS stat item of each hnb_persistent */ static void hnbgw_store_hnb_uptime(void *data) { struct hnb_persistent *hnbp; llist_for_each_entry(hnbp, &g_hnbgw->hnb_persistent_list, list) { HNBP_STAT_SET(hnbp, HNB_STAT_UPTIME_SECONDS, hnbp->ctx != NULL ? hnbp_get_updowntime(hnbp) : 0); } osmo_timer_schedule(&g_hnbgw->store_uptime_timer, STORE_UPTIME_INTERVAL, 0); } /*********************************************************************** * SCTP Socket / stream handling ***********************************************************************/ static int hnb_read_cb(struct osmo_stream_srv *conn) { struct hnb_context *hnb = osmo_stream_srv_get_data(conn); struct osmo_fd *ofd = osmo_stream_srv_get_ofd(conn); struct msgb *msg = msgb_alloc(IUH_MSGB_SIZE, "Iuh rx"); int rc; if (!msg) return -ENOMEM; OSMO_ASSERT(hnb); /* we store a reference to the HomeNodeB in the msg->dest for the * benefit of various downstream processing functions */ msg->dst = hnb; rc = osmo_stream_srv_recv(conn, msg); /* Notification received */ if (msgb_sctp_msg_flags(msg) & OSMO_STREAM_SCTP_MSG_FLAGS_NOTIFICATION) { union sctp_notification *notif = (union sctp_notification *)msgb_data(msg); rc = 0; switch (notif->sn_header.sn_type) { case SCTP_ASSOC_CHANGE: switch (notif->sn_assoc_change.sac_state) { case SCTP_COMM_LOST: LOGHNB(hnb, DMAIN, LOGL_NOTICE, "sctp_recvmsg(%s) = SCTP_COMM_LOST, closing conn\n", osmo_sock_get_name2(ofd->fd)); osmo_stream_srv_destroy(conn); rc = -EBADF; break; case SCTP_RESTART: LOGHNB(hnb, DMAIN, LOGL_NOTICE, "HNB SCTP conn RESTARTed, marking as HNBAP-unregistered\n"); hnb->hnb_registered = false; hnb_context_release_ue_state(hnb); /* The tx queue may be quite full after an SCTP RESTART: (SYS#6113) * The link may have been flaky (a possible reason for the peer restarting the conn) and * hence the kernel socket Tx queue may be full (no ACKs coming back) and our own userspace * queue may contain plenty of oldish messages to be sent. Since the HNB will re-register after * this, we simply drop all those old messages: */ osmo_stream_srv_clear_tx_queue(conn); break; } break; case SCTP_SHUTDOWN_EVENT: LOGHNB(hnb, DMAIN, LOGL_NOTICE, "sctp_recvmsg(%s) = SCTP_SHUTDOWN_EVENT, closing conn\n", osmo_sock_get_name2(ofd->fd)); osmo_stream_srv_destroy(conn); rc = -EBADF; break; } goto out; } else if (rc == -EAGAIN) { /* Older versions of osmo_stream_srv_recv() not supporting * msgb_sctp_msg_flags() may still return -EAGAIN when an sctp * notification is received. */ rc = 0; goto out; } else if (rc < 0) { LOGHNB(hnb, DMAIN, LOGL_ERROR, "Error during sctp_recvmsg(%s)\n", osmo_sock_get_name2(ofd->fd)); osmo_stream_srv_destroy(conn); rc = -EBADF; goto out; } else if (rc == 0) { LOGHNB(hnb, DMAIN, LOGL_NOTICE, "Connection closed sctp_recvmsg(%s) = 0\n", osmo_sock_get_name2(ofd->fd)); osmo_stream_srv_destroy(conn); rc = -EBADF; goto out; } else { msgb_put(msg, rc); } switch (msgb_sctp_ppid(msg)) { case IUH_PPI_HNBAP: hnb->hnbap_stream = msgb_sctp_stream(msg); rc = hnbgw_hnbap_rx(hnb, msg); break; case IUH_PPI_RUA: if (!hnb->hnb_registered) { LOGHNB(hnb, DMAIN, LOGL_NOTICE, "Discarding RUA as HNB is not registered\n"); goto out; } hnb->rua_stream = msgb_sctp_stream(msg); rc = hnbgw_rua_rx(hnb, msg); break; case IUH_PPI_SABP: case IUH_PPI_RNA: case IUH_PPI_PUA: LOGHNB(hnb, DMAIN, LOGL_ERROR, "Unimplemented SCTP PPID=%lu received\n", msgb_sctp_ppid(msg)); rc = 0; break; default: LOGHNB(hnb, DMAIN, LOGL_ERROR, "Unknown SCTP PPID=%lu received\n", msgb_sctp_ppid(msg)); rc = 0; break; } out: msgb_free(msg); return rc; } static int hnb_closed_cb(struct osmo_stream_srv *conn) { struct hnb_context *hnb = osmo_stream_srv_get_data(conn); if (!hnb) return 0; /* hnb_context is being freed, nothing do be done */ /* hnb: conn became broken, let's release the associated hnb. * conn object is being freed after closed_cb(), so unassign it from hnb * if available to avoid it freeing it again: */ hnb->conn = NULL; hnb_context_release(hnb); return 0; } /*! call-back when the listen FD has something to read */ int hnbgw_rua_accept_cb(struct osmo_stream_srv_link *srv, int fd) { struct hnb_context *ctx; LOGP(DMAIN, LOGL_INFO, "New HNB SCTP connection %s\n", osmo_sock_get_name2(fd)); ctx = hnb_context_alloc(srv, fd); if (!ctx) return -ENOMEM; return 0; } CTRL_CMD_DEFINE_RO(hnb_info, "info"); static int get_hnb_info(struct ctrl_cmd *cmd, void *data) { struct hnb_context *hnb = data; cmd->reply = talloc_strdup(cmd, hnb->identity_info); return CTRL_CMD_REPLY; } CTRL_CMD_DEFINE_RO(hnbs, "num-hnb"); static int get_hnbs(struct ctrl_cmd *cmd, void *data) { cmd->reply = talloc_asprintf(cmd, "%u", llist_count(&g_hnbgw->hnb_list)); return CTRL_CMD_REPLY; } int hnb_ctrl_cmds_install(void) { int rc = 0; rc |= ctrl_cmd_install(CTRL_NODE_ROOT, &cmd_hnbs); rc |= ctrl_cmd_install(CTRL_NODE_HNB, &cmd_hnb_info); return rc; } int hnb_ctrl_node_lookup(void *data, vector vline, int *node_type, void **node_data, int *i) { const char *token = vector_slot(vline, *i); struct hnb_context *hnb; long num; switch (*node_type) { case CTRL_NODE_ROOT: if (strcmp(token, "hnb") != 0) return 0; (*i)++; if (!ctrl_parse_get_num(vline, *i, &num)) return -ERANGE; hnb = hnb_context_by_id(num); if (!hnb) return -ENODEV; *node_data = hnb; *node_type = CTRL_NODE_HNB; break; default: return 0; } return 1; } int hnbgw_mgw_setup(void) { struct mgcp_client *mgcp_client_single; unsigned int pool_members_initalized; /* Initialize MGW pool. This initalizes and connects all MGCP clients that are currently configured in * the pool. Adding additional MGCP clients to the pool is possible but the user has to configure and * (re)connect them manually from the VTY. */ if (!mgcp_client_pool_empty(g_hnbgw->mgw_pool)) { pool_members_initalized = mgcp_client_pool_connect(g_hnbgw->mgw_pool); if (!pool_members_initalized) { LOGP(DMGW, LOGL_ERROR, "MGW pool failed to initialize any pool members\n"); return -EINVAL; } LOGP(DMGW, LOGL_NOTICE, "MGW pool with %u pool members configured, (ignoring MGW configuration in VTY node 'mgcp').\n", pool_members_initalized); return 0; } /* Initialize and connect a single MGCP client. This MGCP client will appear as the one and only pool * member if there is no MGW pool configured. */ LOGP(DMGW, LOGL_NOTICE, "No MGW pool configured, using MGW configuration in VTY node 'mgcp'\n"); mgcp_client_single = mgcp_client_init(g_hnbgw, g_hnbgw->config.mgcp_client); if (!mgcp_client_single) { LOGP(DMGW, LOGL_ERROR, "MGW (single) client initalization failed\n"); return -EINVAL; } if (mgcp_client_connect(mgcp_client_single)) { LOGP(DMGW, LOGL_ERROR, "MGW (single) connect failed at (%s:%u)\n", g_hnbgw->config.mgcp_client->remote_addr, g_hnbgw->config.mgcp_client->remote_port); return -EINVAL; } mgcp_client_pool_register_single(g_hnbgw->mgw_pool, mgcp_client_single); return 0; } struct msgb *hnbgw_ranap_msg_alloc(const char *name) { struct msgb *ranap_msg; ranap_msg = msgb_alloc_c(OTC_SELECT, sizeof(struct osmo_scu_prim) + 1500, name); msgb_reserve(ranap_msg, sizeof(struct osmo_scu_prim)); ranap_msg->l2h = ranap_msg->data; return ranap_msg; } #define HNBGW_COPYRIGHT \ "OsmoHNBGW - Osmocom Home Node B Gateway implementation\r\n" \ "Copyright (C) 2016-2024 by sysmocom s.f.m.c. GmbH \r\n" \ "Contributions by Daniel Willmann, Harald Welte, Neels Hofmeyr\r\n" \ "License AGPLv3+: GNU AGPL version 3 or later \r\n" \ "This is free software: you are free to change and redistribute it.\r\n" \ "There is NO WARRANTY, to the extent permitted by law.\r\n" static const struct log_info_cat hnbgw_log_cat[] = { [DMAIN] = { .name = "DMAIN", .loglevel = LOGL_NOTICE, .enabled = 1, .color = "", .description = "Main program", }, [DHNBAP] = { .name = "DHNBAP", .loglevel = LOGL_NOTICE, .enabled = 1, .color = "", .description = "Home Node B Application Part", }, [DRUA] = { .name = "DRUA", .loglevel = LOGL_NOTICE, .enabled = 1, .color = "", .description = "RANAP User Adaptation", }, [DRANAP] = { .name = "DRANAP", .loglevel = LOGL_NOTICE, .enabled = 1, .color = "", .description = "RAN Application Part", }, [DMGW] = { .name = "DMGW", .loglevel = LOGL_NOTICE, .enabled = 1, .color = "\033[1;33m", .description = "Media Gateway", }, [DHNB] = { .name = "DHNB", .loglevel = LOGL_NOTICE, .enabled = 1, .color = OSMO_LOGCOLOR_CYAN, .description = "HNB side (via RUA)", }, [DCN] = { .name = "DCN", .loglevel = LOGL_NOTICE, .enabled = 1, .color = OSMO_LOGCOLOR_DARKYELLOW, .description = "Core Network side (via SCCP)", }, [DNFT] = { .name = "DNFT", .loglevel = LOGL_NOTICE, .enabled = 1, .color = OSMO_LOGCOLOR_BLUE, .description = "nftables interaction for retrieving stats", }, }; const struct log_info hnbgw_log_info = { .cat = hnbgw_log_cat, .num_cat = ARRAY_SIZE(hnbgw_log_cat), }; struct vty_app_info hnbgw_vty_info = { .name = "OsmoHNBGW", .version = PACKAGE_VERSION, .go_parent_cb = hnbgw_vty_go_parent, .copyright = HNBGW_COPYRIGHT, }; void g_hnbgw_alloc(void *ctx) { OSMO_ASSERT(!g_hnbgw); g_hnbgw = talloc_zero(ctx, struct hnbgw); /* strdup so we can easily talloc_free in the VTY code */ g_hnbgw->config.iuh_local_ip = talloc_strdup(g_hnbgw, HNBGW_LOCAL_IP_DEFAULT); g_hnbgw->config.iuh_local_port = IUH_DEFAULT_SCTP_PORT; g_hnbgw->config.log_prefix_hnb_id = true; g_hnbgw->config.accept_all_hnb = true; /* Set zero PLMN to detect a missing PLMN when transmitting RESET */ g_hnbgw->config.plmn = (struct osmo_plmn_id){ 0, 0, false }; g_hnbgw->next_ue_ctx_id = 23; INIT_LLIST_HEAD(&g_hnbgw->hnb_list); INIT_LLIST_HEAD(&g_hnbgw->hnb_persistent_list); INIT_LLIST_HEAD(&g_hnbgw->ue_list); INIT_LLIST_HEAD(&g_hnbgw->sccp.users); g_hnbgw->mgw_pool = mgcp_client_pool_alloc(g_hnbgw); g_hnbgw->config.mgcp_client = mgcp_client_conf_alloc(g_hnbgw); #if ENABLE_PFCP g_hnbgw->config.pfcp.remote_port = OSMO_PFCP_PORT; #endif g_hnbgw->sccp.cnpool_iucs = (struct hnbgw_cnpool){ .domain = DOMAIN_CS, .pool_name = "iucs", .peer_name = "msc", .default_remote_pc = DEFAULT_PC_MSC, .vty = { .nri_bitlen = OSMO_NRI_BITLEN_DEFAULT, .null_nri_ranges = osmo_nri_ranges_alloc(g_hnbgw), }, .cnlink_ctrg_desc = &msc_ctrg_desc, .ctrs = rate_ctr_group_alloc(g_hnbgw, &iucs_ctrg_desc, 0), }; INIT_LLIST_HEAD(&g_hnbgw->sccp.cnpool_iucs.cnlinks); g_hnbgw->sccp.cnpool_iups = (struct hnbgw_cnpool){ .domain = DOMAIN_PS, .pool_name = "iups", .peer_name = "sgsn", .default_remote_pc = DEFAULT_PC_SGSN, .vty = { .nri_bitlen = OSMO_NRI_BITLEN_DEFAULT, .null_nri_ranges = osmo_nri_ranges_alloc(g_hnbgw), }, .cnlink_ctrg_desc = &sgsn_ctrg_desc, .ctrs = rate_ctr_group_alloc(g_hnbgw, &iups_ctrg_desc, 0), }; INIT_LLIST_HEAD(&g_hnbgw->sccp.cnpool_iups.cnlinks); osmo_timer_setup(&g_hnbgw->store_uptime_timer, hnbgw_store_hnb_uptime, g_hnbgw); osmo_timer_schedule(&g_hnbgw->store_uptime_timer, STORE_UPTIME_INTERVAL, 0); osmo_timer_setup(&g_hnbgw->hnb_store_rab_durations_timer, hnbgw_store_hnb_rab_durations, g_hnbgw); osmo_timer_schedule(&g_hnbgw->hnb_store_rab_durations_timer, HNB_STORE_RAB_DURATIONS_INTERVAL, 0); }