diff --git a/include/osmocom/hlr/hlr_sms.h b/include/osmocom/hlr/hlr_sms.h index 8e06b9ed..dc72d323 100644 --- a/include/osmocom/hlr/hlr_sms.h +++ b/include/osmocom/hlr/hlr_sms.h @@ -26,3 +26,13 @@ struct hlr_sms_route *sms_route_alloc(struct hlr *hlr, const char *pattern, const struct hlr_euse *euse); void sms_route_del(struct hlr_sms_route *rt); + +int forward_mo_sms(struct osmo_gsup_conn *conn, + const struct osmo_gsup_message *gsup, + struct msgb *msg); +int forward_sm_res_or_err(struct osmo_gsup_conn *conn, + const struct osmo_gsup_message *gsup, + struct msgb *msg); +int forward_mt_sms(struct osmo_gsup_conn *conn, + const struct osmo_gsup_message *gsup, + struct msgb *msg); diff --git a/src/hlr.c b/src/hlr.c index 770590c1..22ad406a 100644 --- a/src/hlr.c +++ b/src/hlr.c @@ -47,6 +47,7 @@ #include #include #include +#include struct hlr *g_hlr; static void *hlr_ctx = NULL; @@ -663,6 +664,28 @@ static int read_cb(struct osmo_gsup_conn *conn, struct msgb *msg) case OSMO_GSUP_MSGT_PROC_SS_ERROR: rx_proc_ss_error(conn, &gsup); break; + + /* Short Message from MSC/VLR towards SMSC */ + case OSMO_GSUP_MSGT_MO_FORWARD_SM_REQUEST: + case OSMO_GSUP_MSGT_READY_FOR_SM_REQUEST: + forward_mo_sms(conn, &gsup, msg); + return 0; /* Do not free msgb */ + + /* Short Message from SMSC towards MSC/VLR */ + case OSMO_GSUP_MSGT_MT_FORWARD_SM_REQUEST: + forward_mt_sms(conn, &gsup, msg); + return 0; /* Do not free msgb */ + + /* Short Message delivery status, to be forwarded 'as-is' */ + case OSMO_GSUP_MSGT_MO_FORWARD_SM_RESULT: + case OSMO_GSUP_MSGT_MT_FORWARD_SM_RESULT: + case OSMO_GSUP_MSGT_MO_FORWARD_SM_ERROR: + case OSMO_GSUP_MSGT_MT_FORWARD_SM_ERROR: + case OSMO_GSUP_MSGT_READY_FOR_SM_RESULT: + case OSMO_GSUP_MSGT_READY_FOR_SM_ERROR: + forward_sm_res_or_err(conn, &gsup, msg); + return 0; /* Do not free msgb */ + case OSMO_GSUP_MSGT_INSERT_DATA_ERROR: case OSMO_GSUP_MSGT_INSERT_DATA_RESULT: case OSMO_GSUP_MSGT_LOCATION_CANCEL_ERROR: diff --git a/src/hlr_sms.c b/src/hlr_sms.c index 7e2c1456..8ee01f28 100644 --- a/src/hlr_sms.c +++ b/src/hlr_sms.c @@ -24,6 +24,10 @@ #include #include +#include + +#include +#include #include #include @@ -77,3 +81,318 @@ void sms_route_del(struct hlr_sms_route *rt) llist_del(&rt->list); talloc_free(rt); } + +/* Common helper for preparing to be encoded GSUP message */ +static void gsup_prepare_sm_error(struct osmo_gsup_message *msg, + const struct osmo_gsup_message *src_msg) +{ + /* Init a mew GSUP message */ + *msg = (struct osmo_gsup_message) { + .message_type = OSMO_GSUP_TO_MSGT_ERROR(src_msg->message_type), + .message_class = OSMO_GSUP_MESSAGE_CLASS_SMS, + .sm_rp_mr = src_msg->sm_rp_mr, + + /* Swap optional source and destination addresses */ + .destination_name_len = src_msg->source_name_len, + .destination_name = src_msg->source_name, + }; + + /* Fill in subscriber's IMSI */ + OSMO_STRLCPY_ARRAY(msg->imsi, src_msg->imsi); +} + +static int gsup_conn_enc_send(struct osmo_gsup_conn *conn, + struct osmo_gsup_message *msg) +{ + struct msgb *gsup_msgb; + int rc; + + gsup_msgb = msgb_alloc_headroom(512, 64, __func__); + if (!gsup_msgb) { + LOGP(DLSMS, LOGL_ERROR, "Failed to allocate a GSUP message\n"); + return -ENOMEM; + } + + rc = osmo_gsup_encode(gsup_msgb, msg); + if (rc) { + LOGP(DLSMS, LOGL_ERROR, "Failed to encode GSUP message '%s' (rc=%d)\n", + osmo_gsup_message_type_name(msg->message_type), rc); + return rc; + } + + rc = osmo_gsup_conn_send(conn, gsup_msgb); + if (rc) { + LOGP(DLSMS, LOGL_ERROR, "Failed to send GSUP message '%s' (rc=%d)\n", + osmo_gsup_message_type_name(msg->message_type), rc); + return rc; + } + + return 0; +} + +/* Short Message delivery status, to be forwarded 'as-is' */ +int forward_sm_res_or_err(struct osmo_gsup_conn *conn, + const struct osmo_gsup_message *gsup, + struct msgb *msg) +{ + struct hlr_subscriber subscr; + char src_name_buf[32]; + char dst_name_buf[32]; + int rc; + + rc = db_subscr_get_by_imsi(g_hlr->dbc, gsup->imsi, &subscr); + if (rc) { + LOGP(DLSMS, LOGL_NOTICE, "Rx '%s' for unknown subscriber IMSI-%s\n", + osmo_gsup_message_type_name(gsup->message_type), gsup->imsi); + /* TODO: send some error back? */ + msgb_free(msg); + return -ENODEV; + } + + /* Make sure destination name is present */ + if (gsup->destination_name == NULL || !gsup->destination_name_len) { + LOGP(DLSMS, LOGL_NOTICE, "Rx '%s' (IMSI-%s) without destination name\n", + osmo_gsup_message_type_name(gsup->message_type), gsup->imsi); + /* TODO: send some error back? */ + msgb_free(msg); + return -EINVAL; + } + + /* Make sure source name is present */ + if (gsup->source_name == NULL || !gsup->source_name_len) { + LOGP(DLSMS, LOGL_NOTICE, "Rx '%s' (IMSI-%s) without source name\n", + osmo_gsup_message_type_name(gsup->message_type), gsup->imsi); + /* TODO: send some error back? */ + msgb_free(msg); + return -EINVAL; + } + + LOGP(DLSMS, LOGL_INFO, "Forward '%s' (IMSI-%s) from %s to %s\n", + osmo_gsup_message_type_name(gsup->message_type), gsup->imsi, + osmo_quote_str_buf2(src_name_buf, sizeof(src_name_buf), + (const char *) gsup->source_name, + gsup->source_name_len), + osmo_quote_str_buf2(dst_name_buf, sizeof(dst_name_buf), + (const char *) gsup->destination_name, + gsup->destination_name_len)); + + rc = osmo_gsup_addr_send(conn->server, gsup->destination_name, + gsup->destination_name_len, msg); + if (rc) { + LOGP(DLSMS, LOGL_NOTICE, "Failed to forward '%s' (IMSI-%s)\n", + osmo_gsup_message_type_name(gsup->message_type), gsup->imsi); + /* osmo_gsup_addr_send() free()d msg */ + return rc; + } + + return 0; +} + +static const struct hlr_euse *find_euse(const struct hlr_subscriber *subscr, + const char *smsc_addr) +{ + struct hlr_sms_route *rt; + const char *pattern; + + /* Iterate over all known routes */ + llist_for_each_entry(rt, &g_hlr->sms_routes, list) { + switch (rt->type) { + case HLR_SMS_RT_SMSC_ADDR: + pattern = smsc_addr; + break; + case HLR_SMS_RT_SENDER_MSISDN: + pattern = subscr->msisdn; + break; + case HLR_SMS_RT_SENDER_IMSI: + pattern = subscr->imsi; + break; + default: + /* Shall not happen, make Coverity happy */ + continue; + } + + if (strcmp(rt->match_pattern, pattern) == 0) + return rt->euse; + } + + /* Fall-back to default route if nothing will be found */ + return g_hlr->sms_euse_default; +} + +static const struct osmo_gsup_conn *find_conn(struct osmo_gsup_server *srv, + const struct hlr_euse *euse) +{ + char euse_addr[128]; + int rc; + + rc = snprintf(euse_addr, sizeof(euse_addr), "EUSE-%s", euse->name); + return gsup_route_find(srv, (uint8_t *) euse_addr, rc + 1); +} + +/* Short Message from MSC/VLR towards SMSC */ +int forward_mo_sms(struct osmo_gsup_conn *conn, + const struct osmo_gsup_message *gsup, + struct msgb *msg) +{ + char smsc_addr[GSM23003_MSISDN_MAX_DIGITS + 1]; + struct osmo_gsup_message rsp_msg; + struct hlr_subscriber subscr; + uint8_t ext, ton, npi; + uint8_t sm_rp_cause; + int rc; + + rc = db_subscr_get_by_imsi(g_hlr->dbc, gsup->imsi, &subscr); + if (rc) { + LOGP(DLSMS, LOGL_NOTICE, "Rx '%s' for unknown subscriber IMSI-%s\n", + osmo_gsup_message_type_name(gsup->message_type), gsup->imsi); + gsup_prepare_sm_error(&rsp_msg, gsup); + rsp_msg.cause = GMM_CAUSE_IMSI_UNKNOWN; + goto exit_error; + } + + /* Make sure SM-RP-DA (SMSC address) is present */ + if (gsup->sm_rp_da == NULL || !gsup->sm_rp_da_len) { + LOGP(DLSMS, LOGL_NOTICE, "Rx '%s' (IMSI-%s) without mandatory SM-RP-DA\n", + osmo_gsup_message_type_name(gsup->message_type), gsup->imsi); + gsup_prepare_sm_error(&rsp_msg, gsup); + rsp_msg.cause = GMM_CAUSE_INV_MAND_INFO; + goto exit_error; + } + + if (gsup->sm_rp_da_type != OSMO_GSUP_SMS_SM_RP_ODA_SMSC_ADDR) { + LOGP(DLSMS, LOGL_NOTICE, "Rx '%s' (IMSI-%s) with unexpected SM-RP-DA 0x%02x\n", + osmo_gsup_message_type_name(gsup->message_type), gsup->imsi, + gsup->sm_rp_da_type); + gsup_prepare_sm_error(&rsp_msg, gsup); + rsp_msg.cause = GMM_CAUSE_INV_MAND_INFO; + goto exit_error; + } + + /* Parse ToN (Type of Number) / NPI (Numbering Plan Indicator) */ + ext = (gsup->sm_rp_da[0] >> 7) ^ 0x01; /* NOTE: inversed */ + ton = (gsup->sm_rp_da[0] >> 4) & 0x07; + npi = gsup->sm_rp_da[0] & 0x0f; + + /* We only support International ISDN/telephone format */ + if (ext || ton != 0x01 || npi != 0x01) { + LOGP(DLSMS, LOGL_NOTICE, "Rx '%s' (IMSI-%s) with unsupported SMSC address format: " + "ToN=0x%02x, NPI=0x%02x\n, extension=%s\n", + osmo_gsup_message_type_name(gsup->message_type), gsup->imsi, + ton, npi, ext ? "yes" : "no"); + gsup_prepare_sm_error(&rsp_msg, gsup); + rsp_msg.cause = GMM_CAUSE_SEM_INCORR_MSG; + goto exit_error; + } + + /* Decode SMSC address from SM-RP-DA */ + rc = osmo_bcd2str(smsc_addr, sizeof(smsc_addr), gsup->sm_rp_da + 1, + 2, (gsup->sm_rp_da_len - 1) * 2, true); + if (rc < 0 || rc >= sizeof(smsc_addr)) { + LOGP(DLSMS, LOGL_NOTICE, "Failed to decode SMSC address from '%s' (IMSI-%s): rc=%d\n", + osmo_gsup_message_type_name(gsup->message_type), gsup->imsi, rc); + gsup_prepare_sm_error(&rsp_msg, gsup); + rsp_msg.cause = GMM_CAUSE_SEM_INCORR_MSG; + goto exit_error; + } + + /* Attempt to find a EUSE */ + const struct hlr_euse *euse = find_euse(&subscr, smsc_addr); + if (euse == NULL) { + LOGP(DLSMS, LOGL_NOTICE, "Failed to find a route for '%s' (IMSI-%s, MR-0x%02x)\n", + osmo_gsup_message_type_name(gsup->message_type), gsup->imsi, *gsup->sm_rp_mr); + gsup_prepare_sm_error(&rsp_msg, gsup); + sm_rp_cause = GSM411_RP_CAUSE_MO_NUM_UNASSIGNED; + rsp_msg.sm_rp_cause = &sm_rp_cause; + goto exit_error; + } + + const struct osmo_gsup_conn *euse_conn = find_conn(conn->server, euse); + if (euse_conn == NULL) { + LOGP(DLSMS, LOGL_ERROR, "EUSE '%s' is not connected!\n", euse->name); + gsup_prepare_sm_error(&rsp_msg, gsup); + sm_rp_cause = GSM411_RP_CAUSE_MO_TEMP_FAIL; + rsp_msg.sm_rp_cause = &sm_rp_cause; + goto exit_error; + } + + LOGP(DLSMS, LOGL_INFO, "Forwarding '%s' (IMSI-%s, MR-0x%02x) to SMSC '%s'\n", + osmo_gsup_message_type_name(gsup->message_type), gsup->imsi, + *gsup->sm_rp_mr, euse->name); + + /* HACK: make sure source name is present, fill in if needed */ + if (gsup->source_name == NULL || !gsup->source_name_len) { + /* FIXME: distinguish between MSC/VLR and SGSN */ + msgb_tlv_put(msg, OSMO_GSUP_SOURCE_NAME_IE, + strlen(subscr.vlr_number) + 1, + (uint8_t *) subscr.vlr_number); + } + + /* Ensure the buffer has enough headroom to put IPA headers */ + msgb_pull_to_l2(msg); + + /* Finally forward the original message */ + rc = osmo_gsup_conn_send((struct osmo_gsup_conn *) euse_conn, msg); + if (rc) { + LOGP(DLSMS, LOGL_ERROR, "Failed to send GSUP message '%s' (rc=%d)\n", + osmo_gsup_message_type_name(gsup->message_type), rc); + gsup_prepare_sm_error(&rsp_msg, gsup); + sm_rp_cause = GSM411_RP_CAUSE_MO_TEMP_FAIL; + rsp_msg.sm_rp_cause = &sm_rp_cause; + msg = NULL; /* free()d by osmo_gsup_conn_send() */ + goto exit_error; + } + + return 0; + +exit_error: + gsup_conn_enc_send(conn, &rsp_msg); + if (msg != NULL) + talloc_free(msg); + return rc; +} + +/* Short Message from SMSC towards MSC/VLR */ +int forward_mt_sms(struct osmo_gsup_conn *conn, + const struct osmo_gsup_message *gsup, + struct msgb *msg) +{ + struct osmo_gsup_message rsp_msg; + struct hlr_subscriber subscr; + uint8_t sm_rp_cause; + int rc; + + rc = db_subscr_get_by_imsi(g_hlr->dbc, gsup->imsi, &subscr); + if (rc) { + LOGP(DLSMS, LOGL_NOTICE, "Rx '%s' for unknown subscriber IMSI-%s\n", + osmo_gsup_message_type_name(gsup->message_type), gsup->imsi); + gsup_prepare_sm_error(&rsp_msg, gsup); + rsp_msg.cause = GMM_CAUSE_IMSI_UNKNOWN; + goto exit_error; + } + +#if 0 + LOGP(DLSMS, LOGL_INFO, "Forwarding '%s' (IMSI-%s, MR-0x%02x) to MSC/VLR '%s'\n", + osmo_gsup_message_type_name(gsup->message_type), gsup->imsi, + *gsup->sm_rp_mr, FIXME!); + + /* Finally forward the original message */ + rc = osmo_gsup_conn_send(subscr_conn, msg); + if (rc) { + LOGP(DLSMS, LOGL_ERROR, "Failed to send GSUP message '%s' (rc=%d)\n", + osmo_gsup_message_type_name(gsup->message_type), rc); + gsup_prepare_sm_error(&rsp_msg, gsup); + sm_rp_cause = GSM411_RP_CAUSE_MO_TEMP_FAIL; + rsp_msg.sm_rp_cause = &sm_rp_cause; + msg = NULL; /* free()d by osmo_gsup_conn_send() */ + goto exit_error; + } +#endif + + return 0; + +exit_error: + gsup_conn_enc_send(conn, &rsp_msg); + if (msg != NULL) + talloc_free(msg); + return rc; +}