#pragma once #include #include #include #include #include #include #include #include #include #include #include #include #define DEBUG #include #include #include #include #define STORE_UPTIME_INTERVAL 10 /* seconds */ #define HNB_STORE_RAB_DURATIONS_INTERVAL 1 /* seconds */ enum { DMAIN, DHNBAP, DRUA, DRANAP, DMGW, DHNB, DCN, DNFT, }; extern const struct log_info hnbgw_log_info; extern struct vty_app_info hnbgw_vty_info; #define LOGHNB(HNB_CTX, ss, lvl, fmt, args ...) \ LOGP(ss, lvl, "(%s) " fmt, hnb_context_name(HNB_CTX), ## args) #define DOMAIN_CS RANAP_CN_DomainIndicator_cs_domain #define DOMAIN_PS RANAP_CN_DomainIndicator_ps_domain extern const struct value_string ranap_domain_names[]; static inline const char *ranap_domain_name(RANAP_CN_DomainIndicator_t domain) { return get_value_string(ranap_domain_names, domain); } enum hnb_ctrl_node { CTRL_NODE_HNB = _LAST_CTRL_NODE, _LAST_CTRL_NODE_HNB }; #define HNBGW_LOCAL_IP_DEFAULT "0.0.0.0" /* TODO: CS and PS now both connect to OsmoSTP, i.e. that's always going to be the same address. Drop the * duplicity. */ #define HNBGW_IUCS_REMOTE_IP_DEFAULT "127.0.0.1" #define HNBGW_IUPS_REMOTE_IP_DEFAULT "127.0.0.1" #define DEFAULT_PC_HNBGW ((23 << 3) + 5) #define DEFAULT_PC_MSC ((23 << 3) + 1) #define DEFAULT_PC_SGSN ((23 << 3) + 4) /* 25.467 Section 7.1 */ #define IUH_DEFAULT_SCTP_PORT 29169 #define RNA_DEFAULT_SCTP_PORT 25471 #define IUH_PPI_RUA 19 #define IUH_PPI_HNBAP 20 #define IUH_PPI_SABP 31 #define IUH_PPI_RNA 42 #define IUH_PPI_PUA 55 #define IUH_MSGB_SIZE 2048 enum hnb_rate_ctr { HNB_CTR_IUH_ESTABLISHED, HNB_CTR_RANAP_PS_ERR_IND_UL, HNB_CTR_RANAP_CS_ERR_IND_UL, HNB_CTR_RANAP_PS_RESET_REQ_UL, HNB_CTR_RANAP_CS_RESET_REQ_UL, HNB_CTR_RANAP_PS_RAB_ACT_REQ, HNB_CTR_RANAP_CS_RAB_ACT_REQ, HNB_CTR_RANAP_PS_RAB_ACT_CNF, HNB_CTR_RANAP_CS_RAB_ACT_CNF, HNB_CTR_RANAP_PS_RAB_ACT_FAIL, HNB_CTR_RANAP_CS_RAB_ACT_FAIL, HNB_CTR_RANAP_PS_RAB_MOD_REQ, HNB_CTR_RANAP_CS_RAB_MOD_REQ, HNB_CTR_RANAP_PS_RAB_MOD_CNF, HNB_CTR_RANAP_CS_RAB_MOD_CNF, HNB_CTR_RANAP_PS_RAB_MOD_FAIL, HNB_CTR_RANAP_CS_RAB_MOD_FAIL, HNB_CTR_RANAP_PS_RAB_REL_REQ, HNB_CTR_RANAP_CS_RAB_REL_REQ, HNB_CTR_RANAP_PS_RAB_REL_CNF, HNB_CTR_RANAP_CS_RAB_REL_CNF, HNB_CTR_RANAP_PS_RAB_REL_FAIL, HNB_CTR_RANAP_CS_RAB_REL_FAIL, HNB_CTR_RANAP_PS_RAB_REL_IMPLICIT, HNB_CTR_RANAP_CS_RAB_REL_IMPLICIT, HNB_CTR_RUA_ERR_IND, HNB_CTR_RUA_PS_CONNECT_UL, HNB_CTR_RUA_CS_CONNECT_UL, HNB_CTR_RUA_PS_DISCONNECT_UL, HNB_CTR_RUA_CS_DISCONNECT_UL, HNB_CTR_RUA_PS_DISCONNECT_DL, HNB_CTR_RUA_CS_DISCONNECT_DL, HNB_CTR_RUA_PS_DT_UL, HNB_CTR_RUA_CS_DT_UL, HNB_CTR_RUA_PS_DT_DL, HNB_CTR_RUA_CS_DT_DL, HNB_CTR_RUA_UDT_UL, HNB_CTR_RUA_UDT_DL, HNB_CTR_PS_PAGING_ATTEMPTED, HNB_CTR_CS_PAGING_ATTEMPTED, HNB_CTR_RAB_ACTIVE_MILLISECONDS_TOTAL, HNB_CTR_GTPU_PACKETS_UL, HNB_CTR_GTPU_TOTAL_BYTES_UL, HNB_CTR_GTPU_PACKETS_DL, HNB_CTR_GTPU_TOTAL_BYTES_DL, }; enum hnb_stat { HNB_STAT_UPTIME_SECONDS, }; struct umts_cell_id { uint16_t mcc; /*!< Mobile Country Code (0-999) */ uint16_t mnc; /*!< Mobile Network Code (0-999) */ uint16_t lac; /*!< Locaton Area Code (1-65534) */ uint16_t rac; /*!< Routing Area Code (0-255) */ uint16_t sac; /*!< Service Area Code */ uint32_t cid; /*!< Cell ID */ }; const char *umts_cell_id_name(const struct umts_cell_id *ucid); int umts_cell_id_from_str(struct umts_cell_id *ucid, const char *instr); /*! are both given umts_cell_id euqal? */ static inline bool umts_cell_id_equal(const struct umts_cell_id *a, const struct umts_cell_id *b) { if (a->mcc != b->mcc) return false; if (a->mnc != b->mnc) return false; if (a->lac != b->lac) return false; if (a->rac != b->rac) return false; if (a->sac != b->sac) return false; if (a->cid != b->cid) return false; return true; } struct hnbgw_context_map; /* osmo-hnbgw keeps a single hnbgw_sccp_user per osmo_sccp_instance, for the local point-code and SSN == RANAP. * This relates the (opaque) osmo_sccp_user to osmo-hnbgw's per-ss7 state. */ struct hnbgw_sccp_user { /* entry in g_hnbgw->sccp.users */ struct llist_head entry; /* logging context */ char *name; /* Which 'cs7 instance' is this for? Below sccp_user is registered at the osmo_sccp_instance ss7->sccp. */ struct osmo_ss7_instance *ss7; /* Local address: cs7 instance's primary PC if present, else the default HNBGW PC; with SSN == RANAP. */ struct osmo_sccp_addr local_addr; /* osmo_sccp API state for above local address on above ss7 instance. */ struct osmo_sccp_user *sccp_user; /* Fast access to the hnbgw_context_map responsible for a given SCCP conn_id of the ss7->sccp instance. * hlist_node: hnbgw_context_map->hnbgw_sccp_user_entry. */ DECLARE_HASHTABLE(hnbgw_context_map_by_conn_id, 6); }; #define LOG_HSI(HNBGW_SCCP_INST, SUBSYS, LEVEL, FMT, ARGS...) \ LOGP(SUBSYS, LEVEL, "(%s) " FMT, (HNBGW_SCCP_INST) ? (HNBGW_SCCP_INST)->name : "null", ##ARGS) /* User provided configuration for struct hnbgw_cnpool. */ struct hnbgw_cnpool_cfg { uint8_t nri_bitlen; struct osmo_nri_ranges *null_nri_ranges; }; /* User provided configuration for struct hnbgw_cnlink. */ struct hnbgw_cnlink_cfg { /* cs7 address book entry to indicate both the remote point-code of the peer, as well as which cs7 instance to * use. */ char *remote_addr_name; struct osmo_nri_ranges *nri_ranges; }; /* Collection of CN peers to distribute UE connections across. MSCs for DOMAIN_CS, SGSNs for DOMAIN_PS. */ struct hnbgw_cnpool { RANAP_CN_DomainIndicator_t domain; /* CN pool string used in VTY config and logging, "iucs" or "iups". */ const char *pool_name; /* CN peer string used in VTY config and logging, "msc" or "sgsn". */ const char *peer_name; /* What we use as the remote MSC/SGSN point-code if the user does not configure any address. */ uint32_t default_remote_pc; struct hnbgw_cnpool_cfg vty; struct hnbgw_cnpool_cfg use; /* List of struct hnbgw_cnlink */ struct llist_head cnlinks; unsigned int round_robin_next_nr; /* Emergency calls potentially select a different set of MSCs, so to not mess up the normal round-robin * behavior, emergency calls need a separate round-robin counter. */ unsigned int round_robin_next_emerg_nr; /* rate counter group that child hnbgw_cnlinks should use (points to msc_ctrg_desc or sgsn_ctrg_desc) */ const struct rate_ctr_group_desc *cnlink_ctrg_desc; /* Running counters for this pool */ struct rate_ctr_group *ctrs; }; #define CNPOOL_CTR_INC(cnpool, x) rate_ctr_inc2((cnpool)->ctrs, x) /* A CN peer, like 'msc 0' or 'sgsn 23' */ struct hnbgw_cnlink { struct llist_head entry; /* backpointer to CS or PS CN pool. */ struct hnbgw_cnpool *pool; struct osmo_fsm_inst *fi; int nr; struct hnbgw_cnlink_cfg vty; struct hnbgw_cnlink_cfg use; /* To print in logging/VTY */ char *name; /* Copy of the address book entry use.remote_addr_name. */ struct osmo_sccp_addr remote_addr; /* The SCCP instance for the cs7 instance indicated by remote_addr_name. (Multiple hnbgw_cnlinks may use the * same hnbgw_sccp_user -- there is exactly one hnbgw_sccp_user per configured cs7 instance.) */ struct hnbgw_sccp_user *hnbgw_sccp_user; /* linked list of hnbgw_context_map */ struct llist_head map_list; bool allow_attach; bool allow_emerg; struct llist_head paging; struct rate_ctr_group *ctrs; }; #define LOG_CNLINK(CNLINK, SUBSYS, LEVEL, FMT, ARGS...) \ LOGP(SUBSYS, LEVEL, "(%s) " FMT, (CNLINK) ? (CNLINK)->name : "null", ##ARGS) #define CNLINK_CTR_INC(cnlink, x) rate_ctr_inc2((cnlink)->ctrs, x) struct hnbgw_cnlink *cnlink_get_nr(struct hnbgw_cnpool *cnpool, int nr, bool create_if_missing); static inline bool cnlink_is_cs(const struct hnbgw_cnlink *cnlink) { return cnlink && cnlink->pool->domain == DOMAIN_CS; } static inline bool cnlink_is_ps(const struct hnbgw_cnlink *cnlink) { return cnlink && cnlink->pool->domain == DOMAIN_PS; } static inline struct osmo_sccp_instance *cnlink_sccp(const struct hnbgw_cnlink *cnlink) { if (!cnlink) return NULL; if (!cnlink->hnbgw_sccp_user) return NULL; if (!cnlink->hnbgw_sccp_user->ss7) return NULL; return cnlink->hnbgw_sccp_user->ss7->sccp; } /* The lifecycle of the hnb_context object is the same as its conn */ struct hnb_context { /*! Entry in HNB-global list of HNB */ struct llist_head list; /*! SCTP socket + write queue for Iuh to this specific HNB */ struct osmo_stream_srv *conn; /*! copied from HNB-Identity-Info IE */ char identity_info[256]; /*! copied from Cell Identity IE */ struct umts_cell_id id; /*! SCTP stream ID for HNBAP */ uint16_t hnbap_stream; /*! SCTP stream ID for RUA */ uint16_t rua_stream; /*! True if a HNB-REGISTER-REQ from this HNB has been accepted. */ bool hnb_registered; /* linked list of hnbgw_context_map */ struct llist_head map_list; /*! pointer to the associated hnb persistent state. Always present after HNB-Register */ struct hnb_persistent *persistent; }; #define HNBP_CTR(hnbp, x) rate_ctr_group_get_ctr((hnbp)->ctrs, x) #define HNBP_CTR_INC(hnbp, x) rate_ctr_inc(HNBP_CTR(hnbp, x)) #define HNBP_CTR_ADD(hnbp, x, y) rate_ctr_add2((hnbp)->ctrs, x, y) #define HNBP_STAT(hbp, x) osmo_stat_item_group_get_item((hnbp)->statg, x) #define HNBP_STAT_SET(hnbp, x, val) osmo_stat_item_set(HNBP_STAT(hnbp, x), val) /* persistent data for one HNB. This continues to exist even as conn / hnb_context is deleted on disconnect */ struct hnb_persistent { /*! Entry in HNBGW-global list of hnb_persistent */ struct llist_head list; /*! back-pointer to hnb_context. Can be NULL if no context at this point */ struct hnb_context *ctx; /*! unique cell identity; copied from HNB REGISTER REQ */ struct umts_cell_id id; /*! stringified version of the cell identiy above (for printing/naming) */ const char *id_str; /*! copied from HNB-Identity-Info IE */ time_t updowntime; struct rate_ctr_group *ctrs; struct osmo_stat_item_group *statg; struct { struct osmo_sockaddr_str addr_remote; struct { struct nft_kpi_val ul; struct nft_kpi_val dl; } last; } nft_kpi; }; struct ue_context { /*! Entry in the HNB-global list of UE */ struct llist_head list; /*! Unique Context ID for this UE */ uint32_t context_id; char imsi[16+1]; uint32_t tmsi; /*! UE is serviced via this HNB */ struct hnb_context *hnb; }; struct hnbgw { struct { const char *iuh_local_ip; /*! SCTP port for Iuh listening */ uint16_t iuh_local_port; /*! The UDP port where we receive multiplexed CS user * plane traffic from HNBs */ uint16_t iuh_cs_mux_port; struct osmo_plmn_id plmn; uint16_t rnc_id; bool hnbap_allow_tmsi; /*! print hnb-id (true) or MCC-MNC-LAC-RAC-SAC (false) in logs */ bool log_prefix_hnb_id; bool accept_all_hnb; struct mgcp_client_conf *mgcp_client; struct { char *local_addr; uint16_t local_port; char *remote_addr; uint16_t remote_port; struct { char *access; char *core; } netinst; } pfcp; } config; /*! SCTP listen socket for incoming connections */ struct osmo_stream_srv_link *iuh; /* list of struct hnb_context */ struct llist_head hnb_list; /* list of struct hnb_persistent */ struct llist_head hnb_persistent_list; struct osmo_timer_list store_uptime_timer; /* list of struct ue_context */ struct llist_head ue_list; /* next availble UE Context ID */ uint32_t next_ue_ctx_id; struct ctrl_handle *ctrl; /* currently active CN links for CS and PS */ struct { /* List of hnbgw_sccp_user */ struct llist_head users; /* Pool of core network peers: MSCs for IuCS */ struct hnbgw_cnpool cnpool_iucs; /* Pool of core network peers: SGSNs for IuPS */ struct hnbgw_cnpool cnpool_iups; } sccp; /* MGW pool, also includes the single MGCP client as fallback if no * pool is configured. */ struct mgcp_client_pool *mgw_pool; struct { struct osmo_pfcp_endpoint *ep; struct osmo_pfcp_cp_peer *cp_peer; } pfcp; struct osmo_timer_list hnb_store_rab_durations_timer; }; extern struct hnbgw *g_hnbgw; extern void *talloc_asn1_ctx; void g_hnbgw_alloc(void *ctx); int hnbgw_rua_accept_cb(struct osmo_stream_srv_link *srv, int fd); int hnb_ctrl_cmds_install(void); int hnb_ctrl_node_lookup(void *data, vector vline, int *node_type, void **node_data, int *i); int hnbgw_mgw_setup(void); struct hnb_context *hnb_context_by_identity_info(const char *identity_info); const char *hnb_context_name(struct hnb_context *ctx); struct ue_context *ue_context_by_id(uint32_t id); struct ue_context *ue_context_by_imsi(const char *imsi); struct ue_context *ue_context_by_tmsi(uint32_t tmsi); struct ue_context *ue_context_alloc(struct hnb_context *hnb, const char *imsi, uint32_t tmsi); void ue_context_free(struct ue_context *ue); void hnb_context_release(struct hnb_context *ctx); void hnb_context_release_ue_state(struct hnb_context *ctx); struct hnb_persistent *hnb_persistent_alloc(const struct umts_cell_id *id); struct hnb_persistent *hnb_persistent_find_by_id(const struct umts_cell_id *id); struct hnb_persistent *hnb_persistent_find_by_id_str(const char *id_str); void hnb_persistent_update_addr(struct hnb_persistent *hnbp, int new_fd); void hnb_persistent_free(struct hnb_persistent *hnbp); void hnbgw_vty_init(void); int hnbgw_vty_go_parent(struct vty *vty); /* Return true when the user configured GTP mapping to be enabled, by configuring a PFCP link to a UPF. * Return false when the user configured to skip GTP mapping and RANAP PS RAB Requests/Responses should be passed thru * 1:1. * GTP mapping means that there are two GTP tunnels, one towards HNB and one towards CN, and we forward payloads between * the two tunnels, mapping the TEIDs and GTP addresses. */ static inline bool hnb_gw_is_gtp_mapping_enabled(void) { return g_hnbgw->config.pfcp.remote_addr != NULL; } struct msgb *hnbgw_ranap_msg_alloc(const char *name); int hnbgw_peek_l3_ul(struct hnbgw_context_map *map, struct msgb *ranap_msg); unsigned long long hnb_get_updowntime(const struct hnb_context *ctx);