From: Sergey Kostanbaev sergey.kostanbaev@gmail.com
This series of patches add support for external USSD processing via simplified MAP-like protocol over SUP socket. As an example of a such external application simple USSD to SIP proxy is provided (it targets TS 124 390)
--- openbsc/include/openbsc/debug.h | 1 + openbsc/src/libcommon/debug.c | 6 ++++++ 2 files changed, 7 insertions(+)
diff --git a/openbsc/include/openbsc/debug.h b/openbsc/include/openbsc/debug.h index 19d8fc2..10b0f46 100644 --- a/openbsc/include/openbsc/debug.h +++ b/openbsc/include/openbsc/debug.h @@ -33,6 +33,7 @@ enum { DCTRL, DSMPP, DFILTER, + DSS, Debug_LastEntry, };
diff --git a/openbsc/src/libcommon/debug.c b/openbsc/src/libcommon/debug.c index 7fb3ecb..6536944 100644 --- a/openbsc/src/libcommon/debug.c +++ b/openbsc/src/libcommon/debug.c @@ -165,6 +165,12 @@ static const struct log_info_cat default_categories[] = { .description = "BSC/NAT IMSI based filtering", .enabled = 1, .loglevel = LOGL_DEBUG, }, + [DSS] = { + .name = "DSS", + .description = "Layer3 Supplementary Service (SS)", + .color = "\033[0;33m", + .enabled = 1, .loglevel = LOGL_DEBUG, + }, };
enum log_filter {
From: Sergey Kostanbaev Sergey.Kostanbaev@fairwaves.co
This message type is used for all USSD MAP-like messages over SUP
--- openbsc/include/openbsc/gprs_gsup_messages.h | 2 ++ 1 file changed, 2 insertions(+)
diff --git a/openbsc/include/openbsc/gprs_gsup_messages.h b/openbsc/include/openbsc/gprs_gsup_messages.h index 8cbc809..fb9c66e 100644 --- a/openbsc/include/openbsc/gprs_gsup_messages.h +++ b/openbsc/include/openbsc/gprs_gsup_messages.h @@ -53,6 +53,8 @@ enum gprs_gsup_iei { };
enum gprs_gsup_message_type { + GPRS_GSUP_MSGT_USSD_MAP = 0b01111111, + GPRS_GSUP_MSGT_UPDATE_LOCATION_REQUEST = 0b00000100, GPRS_GSUP_MSGT_UPDATE_LOCATION_ERROR = 0b00000101, GPRS_GSUP_MSGT_UPDATE_LOCATION_RESULT = 0b00000110,
From: Sergey Kostanbaev sergey.kostanbaev@gmail.com
Use new ss_request structure to support long messages and handle FACILITY message type to support USSD menus.
--- openbsc/include/openbsc/gsm_04_80.h | 11 ++-- openbsc/src/libmsc/gsm_04_80.c | 119 +++++++++++++++++++++++++++--------- openbsc/src/libmsc/ussd.c | 44 ++++++++----- 3 files changed, 128 insertions(+), 46 deletions(-)
diff --git a/openbsc/include/openbsc/gsm_04_80.h b/openbsc/include/openbsc/gsm_04_80.h index 0a60652..08f974f 100644 --- a/openbsc/include/openbsc/gsm_04_80.h +++ b/openbsc/include/openbsc/gsm_04_80.h @@ -7,12 +7,15 @@
struct gsm_subscriber_connection;
+int gsm0480_send_ussd(struct gsm_subscriber_connection *conn, + struct ss_request *req); + int gsm0480_send_ussd_response(struct gsm_subscriber_connection *conn, - const struct msgb *in_msg, const char* response_text, - const struct ussd_request *req); + const char *resp_string, + const struct ss_request *req); + int gsm0480_send_ussd_reject(struct gsm_subscriber_connection *conn, - const struct msgb *msg, - const struct ussd_request *request); + const struct ss_request *request);
int gsm0480_send_ussdNotify(struct gsm_subscriber_connection *conn, int level, const char *text); int gsm0480_send_releaseComplete(struct gsm_subscriber_connection *conn); diff --git a/openbsc/src/libmsc/gsm_04_80.c b/openbsc/src/libmsc/gsm_04_80.c index b30f9ee..4585202 100644 --- a/openbsc/src/libmsc/gsm_04_80.c +++ b/openbsc/src/libmsc/gsm_04_80.c @@ -39,6 +39,22 @@ #include <osmocom/core/msgb.h> #include <osmocom/gsm/tlv.h>
+/* This function can handle ASN1 length up to 255 which is enough for USSD */ +static inline unsigned char *msgb_wrap_with_ASN1_TL(struct msgb *msgb, uint8_t tag) +{ + uint16_t origlen = msgb->len; + uint8_t *data = msgb_push(msgb, (origlen > 0x7f) ? 3 : 2); + data[0] = tag; + if (origlen > 0x7f) { + data[1] = 0x81; + data[2] = origlen; + } else { + data[1] = origlen; + } + return data; +} + + static inline unsigned char *msgb_wrap_with_TL(struct msgb *msgb, uint8_t tag) { uint8_t *data = msgb_push(msgb, 2); @@ -59,59 +75,106 @@ static inline unsigned char *msgb_push_TLV1(struct msgb *msgb, uint8_t tag, return data; }
+static inline unsigned char *msgb_wrap_with_L(struct msgb *msgb) +{ + uint8_t *data = msgb_push(msgb, 1); + + data[0] = msgb->len - 1; + return data; +}
/* Send response to a mobile-originated ProcessUnstructuredSS-Request */ int gsm0480_send_ussd_response(struct gsm_subscriber_connection *conn, - const struct msgb *in_msg, const char *response_text, - const struct ussd_request *req) + const char *resp_string, + const struct ss_request* req) +{ + struct ss_request rss; + int response_len; + + memset(&rss, 0, sizeof(rss)); + + gsm_7bit_encode_n_ussd(rss.ussd_text, MAX_LEN_USSD_STRING, resp_string, &response_len); + rss.ussd_text_len = response_len; + rss.ussd_text_language = 0x0f; + + rss.transaction_id = req->transaction_id; + rss.message_type = GSM0480_MTYPE_RELEASE_COMPLETE; + rss.component_type = GSM0480_CTYPE_RETURN_RESULT; + rss.invoke_id = req->invoke_id; + rss.opcode = GSM0480_OP_CODE_PROCESS_USS_REQ; + + return gsm0480_send_ussd(conn, &rss); +} + +/* Compose universial SS packet except Reject opcodes */ +int gsm0480_send_ussd(struct gsm_subscriber_connection *conn, + struct ss_request* req) { struct msgb *msg = gsm48_msgb_alloc(); struct gsm48_hdr *gh; uint8_t *ptr8; - int response_len;
/* First put the payload text into the message */ ptr8 = msgb_put(msg, 0); - gsm_7bit_encode_n_ussd(ptr8, msgb_tailroom(msg), response_text, &response_len); - msgb_put(msg, response_len); + + memcpy(ptr8, req->ussd_text, req->ussd_text_len); + msgb_put(msg, req->ussd_text_len);
/* Then wrap it as an Octet String */ - msgb_wrap_with_TL(msg, ASN1_OCTET_STRING_TAG); + msgb_wrap_with_ASN1_TL(msg, ASN1_OCTET_STRING_TAG);
/* Pre-pend the DCS octet string */ - msgb_push_TLV1(msg, ASN1_OCTET_STRING_TAG, 0x0F); + msgb_push_TLV1(msg, ASN1_OCTET_STRING_TAG, req->ussd_text_language);
/* Then wrap these as a Sequence */ - msgb_wrap_with_TL(msg, GSM_0480_SEQUENCE_TAG); - - /* Pre-pend the operation code */ - msgb_push_TLV1(msg, GSM0480_OPERATION_CODE, - GSM0480_OP_CODE_PROCESS_USS_REQ); - - /* Wrap the operation code and IA5 string as a sequence */ - msgb_wrap_with_TL(msg, GSM_0480_SEQUENCE_TAG); - - /* Pre-pend the invoke ID */ - msgb_push_TLV1(msg, GSM0480_COMPIDTAG_INVOKE_ID, req->invoke_id); - - /* Wrap this up as a Return Result component */ - msgb_wrap_with_TL(msg, GSM0480_CTYPE_RETURN_RESULT); - - /* Wrap the component in a Facility message */ - msgb_wrap_with_TL(msg, GSM0480_IE_FACILITY); + msgb_wrap_with_ASN1_TL(msg, GSM_0480_SEQUENCE_TAG); + + if (req->component_type == GSM0480_CTYPE_RETURN_RESULT) { + /* Pre-pend the operation code */ + msgb_push_TLV1(msg, GSM0480_OPERATION_CODE, req->opcode); + + /* Wrap the operation code and IA5 string as a sequence */ + msgb_wrap_with_ASN1_TL(msg, GSM_0480_SEQUENCE_TAG); + + /* Pre-pend the invoke ID */ + msgb_push_TLV1(msg, GSM0480_COMPIDTAG_INVOKE_ID, req->invoke_id); + } else if (req->component_type == GSM0480_CTYPE_INVOKE) { + /* Pre-pend the operation code */ + msgb_push_TLV1(msg, GSM0480_OPERATION_CODE, req->opcode); + + /* Pre-pend the invoke ID */ + msgb_push_TLV1(msg, GSM0480_COMPIDTAG_INVOKE_ID, req->invoke_id); + } else { + abort(); + } + + /* Wrap this up as an Invoke or a Return Result component */ + msgb_wrap_with_ASN1_TL(msg, req->component_type); + + if (req->message_type == GSM0480_MTYPE_REGISTER || + req->message_type == GSM0480_MTYPE_RELEASE_COMPLETE) { + /* Wrap the component in a Facility message, it's not ASN1 */ + msgb_wrap_with_TL(msg, GSM0480_IE_FACILITY); + } else if (req->message_type == GSM0480_MTYPE_FACILITY) { + /* For GSM0480_MTYPE_FACILITY it's LV not TLV */ + msgb_wrap_with_L(msg); + } else { + abort(); + }
/* And finally pre-pend the L3 header */ gh = (struct gsm48_hdr *) msgb_push(msg, sizeof(*gh)); gh->proto_discr = GSM48_PDISC_NC_SS | req->transaction_id | (1<<7); /* TI direction = 1 */ - gh->msg_type = GSM0480_MTYPE_RELEASE_COMPLETE; + gh->msg_type = req->message_type; + + DEBUGP(DSS, "Sending USSD to mobile: %s\n", msgb_hexdump(msg));
return gsm0808_submit_dtap(conn, msg, 0, 0); }
int gsm0480_send_ussd_reject(struct gsm_subscriber_connection *conn, - const struct msgb *in_msg, - const struct ussd_request *req) + const struct ss_request *req) { struct msgb *msg = gsm48_msgb_alloc(); struct gsm48_hdr *gh; @@ -124,7 +187,7 @@ int gsm0480_send_ussd_reject(struct gsm_subscriber_connection *conn, msgb_push_TLV1(msg, GSM0480_COMPIDTAG_INVOKE_ID, req->invoke_id);
/* Wrap this up as a Reject component */ - msgb_wrap_with_TL(msg, GSM0480_CTYPE_REJECT); + msgb_wrap_with_ASN1_TL(msg, GSM0480_CTYPE_REJECT);
/* Wrap the component in a Facility message */ msgb_wrap_with_TL(msg, GSM0480_IE_FACILITY); diff --git a/openbsc/src/libmsc/ussd.c b/openbsc/src/libmsc/ussd.c index 7f01eae..f0426c4 100644 --- a/openbsc/src/libmsc/ussd.c +++ b/openbsc/src/libmsc/ussd.c @@ -33,41 +33,57 @@ #include <openbsc/gsm_subscriber.h> #include <openbsc/debug.h> #include <openbsc/osmo_msc.h> +#include <openbsc/ussd.h> +#include <osmocom/gsm/gsm_utils.h> +#include <osmocom/gsm/gsm0480.h>
/* Declarations of USSD strings to be recognised */ const char USSD_TEXT_OWN_NUMBER[] = "*#100#";
/* Forward declarations of network-specific handler functions */ -static int send_own_number(struct gsm_subscriber_connection *conn, const struct msgb *msg, const struct ussd_request *req); +static int send_own_number(struct gsm_subscriber_connection *conn, const struct ss_request *req);
/* Entrypoint - handler function common to all mobile-originated USSDs */ int handle_rcv_ussd(struct gsm_subscriber_connection *conn, struct msgb *msg) { int rc; - struct ussd_request req; + struct ss_request req; + char request_string[MAX_LEN_USSD_STRING + 1]; struct gsm48_hdr *gh;
memset(&req, 0, sizeof(req)); gh = msgb_l3(msg); - rc = gsm0480_decode_ussd_request(gh, msgb_l3len(msg), &req); + rc = gsm0480_decode_ss_request(gh, msgb_l3len(msg), &req); if (!rc) { - DEBUGP(DMM, "Unhandled SS\n"); - rc = gsm0480_send_ussd_reject(conn, msg, &req); + DEBUGP(DSS, "Unhandled SS\n"); + rc = gsm0480_send_ussd_reject(conn, &req); msc_release_connection(conn); return rc; }
- /* Release-Complete */ - if (req.text[0] == '\0') + if (req.message_type == GSM0480_MTYPE_RELEASE_COMPLETE) return 0;
- if (!strcmp(USSD_TEXT_OWN_NUMBER, (const char *)req.text)) { - DEBUGP(DMM, "USSD: Own number requested\n"); - rc = send_own_number(conn, msg, &req); + if (req.message_type != GSM0480_MTYPE_REGISTER || + req.component_type != GSM0480_CTYPE_INVOKE || + req.opcode != GSM0480_OP_CODE_PROCESS_USS_REQ || + req.ussd_text_language != 0x0f) + { + DEBUGP(DSS, "Unexpected SS\n"); + rc = gsm0480_send_ussd_reject(conn, &req); + msc_release_connection(conn); + return rc; + } + + gsm_7bit_decode_n_ussd(request_string, MAX_LEN_USSD_STRING, req.ussd_text, req.ussd_text_len); + + if (!strcmp(USSD_TEXT_OWN_NUMBER, (const char *)request_string)) { + DEBUGP(DSS, "USSD: Own number requested\n"); + rc = send_own_number(conn, &req); } else { - DEBUGP(DMM, "Unhandled USSD %s\n", req.text); - rc = gsm0480_send_ussd_reject(conn, msg, &req); + DEBUGP(DSS, "Unhandled USSD %s\n", request_string); + rc = gsm0480_send_ussd_reject(conn, &req); }
/* check if we can release it */ @@ -76,12 +92,12 @@ int handle_rcv_ussd(struct gsm_subscriber_connection *conn, struct msgb *msg) }
/* A network-specific handler function */ -static int send_own_number(struct gsm_subscriber_connection *conn, const struct msgb *msg, const struct ussd_request *req) +static int send_own_number(struct gsm_subscriber_connection *conn, const struct ss_request *req) { char *own_number = conn->subscr->extension; char response_string[GSM_EXTENSION_LENGTH + 20];
/* Need trailing CR as EOT character */ snprintf(response_string, sizeof(response_string), "Your extension is %s\r", own_number); - return gsm0480_send_ussd_response(conn, msg, response_string, req); + return gsm0480_send_ussd_response(conn, response_string, req); }
From: Sergey Kostanbaev Sergey.Kostanbaev@fairwaves.co
Add simple MAP-like protocol over SUP socket. It forwards USS messages with originated extension and sequence number. Add transaction interface for USSD since now we have async interface for it and USSD session can last for a while in menu scenario.
--- openbsc/include/openbsc/gsm_ussd_map.h | 14 +++ openbsc/include/openbsc/gsm_ussd_map_proto.h | 24 ++++ openbsc/include/openbsc/transaction.h | 5 + openbsc/include/openbsc/ussd.h | 9 ++ openbsc/src/libmsc/Makefile.am | 3 +- openbsc/src/libmsc/gsm_ussd_map.c | 92 ++++++++++++++ openbsc/src/libmsc/gsm_ussd_map_proto.c | 158 ++++++++++++++++++++++++ openbsc/src/libmsc/transaction.c | 4 + openbsc/src/libmsc/ussd.c | 176 +++++++++++++++++++++++++++ 9 files changed, 484 insertions(+), 1 deletion(-) create mode 100644 openbsc/include/openbsc/gsm_ussd_map.h create mode 100644 openbsc/include/openbsc/gsm_ussd_map_proto.h create mode 100644 openbsc/src/libmsc/gsm_ussd_map.c create mode 100644 openbsc/src/libmsc/gsm_ussd_map_proto.c
diff --git a/openbsc/include/openbsc/gsm_ussd_map.h b/openbsc/include/openbsc/gsm_ussd_map.h new file mode 100644 index 0000000..063f421 --- /dev/null +++ b/openbsc/include/openbsc/gsm_ussd_map.h @@ -0,0 +1,14 @@ +#ifndef _GSM_USSD_MAP_H +#define _GSM_USSD_MAP_H + +#include <openbsc/gprs_gsup_client.h> +#include <openbsc/gsm_subscriber.h> +#include <openbsc/gsm_ussd_map_proto.h> + +int ussd_map_read_cb(struct gprs_gsup_client *sup_client, + struct msgb *msg); + +int ussd_map_tx_message(struct gsm_network *net, struct ss_request *req, + const char *extension, uint32_t ref); + +#endif /* _GSM_USSD_MAP_H */ diff --git a/openbsc/include/openbsc/gsm_ussd_map_proto.h b/openbsc/include/openbsc/gsm_ussd_map_proto.h new file mode 100644 index 0000000..8190396 --- /dev/null +++ b/openbsc/include/openbsc/gsm_ussd_map_proto.h @@ -0,0 +1,24 @@ +#ifndef _GSM_USSD_MAP_PROTO_H +#define _GSM_USSD_MAP_PROTO_H + +#include <osmocom/gsm/gsm0480.h> + + +enum { + FMAP_MSISDN = 0x80 +}; + +int subscr_uss_message(struct msgb *msg, + struct ss_request *req, + const char* extension, + uint32_t ref); + +int rx_uss_message_parse(const uint8_t* data, + size_t len, + struct ss_request *ss, + uint32_t *ref, + char* extention, + size_t extention_len); + + +#endif /* _GSM_USSD_MAP_PROTO_H */ diff --git a/openbsc/include/openbsc/transaction.h b/openbsc/include/openbsc/transaction.h index 6ef1612..d82e576 100644 --- a/openbsc/include/openbsc/transaction.h +++ b/openbsc/include/openbsc/transaction.h @@ -56,6 +56,11 @@ struct gsm_trans {
struct gsm_sms *sms; } sms; + struct { + uint8_t invoke_id; + uint8_t mo; + uint8_t dirty; + } ss; }; };
diff --git a/openbsc/include/openbsc/ussd.h b/openbsc/include/openbsc/ussd.h index 2665468..d72d35f 100644 --- a/openbsc/include/openbsc/ussd.h +++ b/openbsc/include/openbsc/ussd.h @@ -5,6 +5,15 @@
#include <osmocom/core/msgb.h>
+#define USSD_MO 1 +#define USSD_MT 0 + int handle_rcv_ussd(struct gsm_subscriber_connection *conn, struct msgb *msg);
+ +int on_ussd_response(struct gsm_network *net, uint32_t ref, struct ss_request* req, const char* extention); + + +void _ussd_trans_free(struct gsm_trans *trans); + #endif diff --git a/openbsc/src/libmsc/Makefile.am b/openbsc/src/libmsc/Makefile.am index 18bfa0c..616ed89 100644 --- a/openbsc/src/libmsc/Makefile.am +++ b/openbsc/src/libmsc/Makefile.am @@ -19,7 +19,8 @@ libmsc_a_SOURCES = auth.c \ ussd.c \ vty_interface_layer3.c \ transaction.c \ - osmo_msc.c ctrl_commands.c meas_feed.c + osmo_msc.c ctrl_commands.c meas_feed.c \ + gsm_ussd_map_proto.c gsm_ussd_map.c
if BUILD_SMPP noinst_HEADERS += smpp_smsc.h diff --git a/openbsc/src/libmsc/gsm_ussd_map.c b/openbsc/src/libmsc/gsm_ussd_map.c new file mode 100644 index 0000000..21604ca --- /dev/null +++ b/openbsc/src/libmsc/gsm_ussd_map.c @@ -0,0 +1,92 @@ +/* GSM USSD external MAP interface */ + +/* (C) 2015 by Sergey Kostanbaev sergey.kostanbaev@gmail.com + * + * 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 http://www.gnu.org/licenses/. + * + */ + +#include <openbsc/gsm_ussd_map.h> +#include <openbsc/gsm_subscriber.h> +#include <openbsc/gsm_04_08.h> +#include <openbsc/debug.h> +#include <openbsc/db.h> +#include <openbsc/chan_alloc.h> +#include <openbsc/gsm_04_08_gprs.h> +#include <openbsc/gprs_gsup_messages.h> +#include <openbsc/gprs_gsup_client.h> +#include <openbsc/osmo_msc.h> +#include <openbsc/gprs_utils.h> +#include <openbsc/ussd.h> + + +int ussd_map_tx_message(struct gsm_network* net, + struct ss_request *req, + const char* extension, + uint32_t ref) +{ + struct msgb *msg = gprs_gsup_msgb_alloc(); + if (!msg) + return -ENOMEM; + + subscr_uss_message(msg, req, extension, ref); + + return gprs_gsup_client_send(net->ussd_sup_client, msg); +} + + +static int ussd_map_rx_message_int(struct gsm_network *net, const uint8_t* data, size_t len) +{ + char extension[32] = {0}; + uint32_t ref; + struct ss_request ss; + memset(&ss, 0, sizeof(ss)); + + if (rx_uss_message_parse(data, len, &ss, &ref, extension, sizeof(extension))) { + LOGP(DSS, LOGL_ERROR, "Can't parse SUP MAP SS message\n"); + return -1; + } + + LOGP(DSS, LOGL_ERROR, "Got invoke_id=0x%02x opcode=0x%02x facility=0x%02x text=%s\n", + ss.invoke_id, ss.opcode, ss.component_type, ss.ussd_text); + + return on_ussd_response(net, ref, &ss, extension); +} + +static int ussd_map_rx_message(struct gprs_gsup_client *sup_client, struct msgb *msg) +{ + uint8_t *data = msgb_l2(msg); + size_t data_len = msgb_l2len(msg); + struct gsm_network *gsmnet = (struct gsm_network *)sup_client->data; + + if (*data != GPRS_GSUP_MSGT_USSD_MAP) { + return -1; + } + + return ussd_map_rx_message_int(gsmnet, data, data_len); +} + +int ussd_map_read_cb(struct gprs_gsup_client *sup_client, struct msgb *msg) +{ + int rc; + + rc = ussd_map_rx_message(sup_client, msg); + msgb_free(msg); + if (rc < 0) + return -1; + + return rc; +} diff --git a/openbsc/src/libmsc/gsm_ussd_map_proto.c b/openbsc/src/libmsc/gsm_ussd_map_proto.c new file mode 100644 index 0000000..4fc0829 --- /dev/null +++ b/openbsc/src/libmsc/gsm_ussd_map_proto.c @@ -0,0 +1,158 @@ +/* GSM USSD external MAP protocol on pseudo TCAP */ + +/* (C) 2015 by Sergey Kostanbaev sergey.kostanbaev@gmail.com + * + * 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 http://www.gnu.org/licenses/. + * + */ + +#include <openbsc/gsm_ussd_map.h> +#include <openbsc/gsm_ussd_map_proto.h> +#include <openbsc/gsm_subscriber.h> +#include <openbsc/gsm_04_08.h> +#include <openbsc/debug.h> +#include <openbsc/db.h> +#include <openbsc/chan_alloc.h> +#include <openbsc/gsm_04_08_gprs.h> +#include <openbsc/gprs_gsup_messages.h> +#include <openbsc/gprs_gsup_client.h> +#include <openbsc/osmo_msc.h> +#include <openbsc/gprs_utils.h> +#include <openbsc/ussd.h> + + +int subscr_uss_message(struct msgb *msg, + struct ss_request *req, + const char* extension, + uint32_t ref) +{ + size_t bcd_len = 0; + uint8_t *gsup_indicator; + + gsup_indicator = msgb_put(msg, 8); + + /* First byte should always be GPRS_GSUP_MSGT_USSD_MAP */ + gsup_indicator[0] = GPRS_GSUP_MSGT_USSD_MAP; + gsup_indicator[1] = req->message_type; + + gsup_indicator[2] = ref >> 24; + gsup_indicator[3] = ref >> 16; + gsup_indicator[4] = ref >> 7; + gsup_indicator[5] = ref; + + gsup_indicator[6] = req->component_type; + + /* invokeId */ + msgb_tlv_put(msg, GSM0480_COMPIDTAG_INVOKE_ID, 1, &req->invoke_id); + + /* opCode */ + msgb_tlv_put(msg, GSM0480_OPERATION_CODE, 1, &req->opcode); + + if (req->ussd_text_len > 0) { + msgb_tlv_put(msg, ASN1_OCTET_STRING_TAG, req->ussd_text_len + 1, &req->ussd_text_language); + } + + if (extension) { + uint8_t bcd_buf[32]; + bcd_len = gsm48_encode_bcd_number(bcd_buf, sizeof(bcd_buf), 0, + extension); + msgb_tlv_put(msg, FMAP_MSISDN, bcd_len - 1, &bcd_buf[1]); + } + + /* fill actual length */ + gsup_indicator[7] = 3 + 3 + (req->ussd_text_len + 1 + 2) + (bcd_len + 2);; + + /* wrap with GSM0480_CTYPE_INVOKE */ + // gsm0480_wrap_invoke(msg, req->opcode, invoke_id); + // gsup_indicator = msgb_push(msgb, 1); + // gsup_indicator[0] = GPRS_GSUP_MSGT_MAP; + return 0; +} + + + +int rx_uss_message_parse(const uint8_t* data, + size_t len, + struct ss_request *ss, + uint32_t *pref, + char* extention, + size_t extention_len) +{ + const uint8_t* const_data = data; + uint32_t ref; + + if (len < 8 + 3 + 3) + return -1; + + /* skip GPRS_GSUP_MSGT_MAP */ + ss->message_type = *(++const_data); + + ref = ((uint32_t)(*(++const_data))) << 24; + ref = ((uint32_t)(*(++const_data))) << 16; + ref = ((uint32_t)(*(++const_data))) << 8; + ref = ((uint32_t)(*(++const_data))); + if (pref) + *pref = ref; + + ss->component_type = *(++const_data); + + /* skip full len and move to component id */ + const_data += 2; + + if (*const_data != GSM0480_COMPIDTAG_INVOKE_ID) { + return -1; + } + const_data += 2; + ss->invoke_id = *const_data; + const_data++; + + // + if (*const_data != GSM0480_OPERATION_CODE) { + return -1; + } + const_data += 2; + ss->opcode = *const_data; + const_data++; + + + while (const_data - data < len) { + uint8_t len; + switch (*const_data) { + case ASN1_OCTET_STRING_TAG: + ss->ussd_text_len = len = (*(++const_data) - 1); + ss->ussd_text_language = *(++const_data); + memcpy(ss->ussd_text, + ++const_data, + (len > MAX_LEN_USSD_STRING) ? MAX_LEN_USSD_STRING : len); + const_data += len; + break; + + case FMAP_MSISDN: + len = *(++const_data); + gsm48_decode_bcd_number(extention, + extention_len, + const_data, + 0); + const_data += len + 1; + break; + default: + DEBUGP(DSS, "Unknown code: %d\n", *const_data); + return -1; + } + } + + return 0; +} diff --git a/openbsc/src/libmsc/transaction.c b/openbsc/src/libmsc/transaction.c index a750362..89cb2b5 100644 --- a/openbsc/src/libmsc/transaction.c +++ b/openbsc/src/libmsc/transaction.c @@ -25,6 +25,7 @@ #include <osmocom/core/talloc.h> #include <openbsc/gsm_subscriber.h> #include <openbsc/gsm_04_08.h> +#include <openbsc/ussd.h> #include <openbsc/mncc.h> #include <openbsc/paging.h> #include <openbsc/osmo_msc.h> @@ -96,6 +97,9 @@ void trans_free(struct gsm_trans *trans) case GSM48_PDISC_SMS: _gsm411_sms_trans_free(trans); break; + case GSM48_PDISC_NC_SS: + _ussd_trans_free(trans); + break; }
if (trans->paging_request) { diff --git a/openbsc/src/libmsc/ussd.c b/openbsc/src/libmsc/ussd.c index f0426c4..f1e373c 100644 --- a/openbsc/src/libmsc/ussd.c +++ b/openbsc/src/libmsc/ussd.c @@ -33,9 +33,21 @@ #include <openbsc/gsm_subscriber.h> #include <openbsc/debug.h> #include <openbsc/osmo_msc.h> +#include <openbsc/gsm_ussd_map.h> #include <openbsc/ussd.h> #include <osmocom/gsm/gsm_utils.h> #include <osmocom/gsm/gsm0480.h> +#include <osmocom/gsm/protocol/gsm_04_08.h> +#include <openbsc/transaction.h> + +/* Counter of open sessions */ +static unsigned s_ussd_open_sessions = 0; + +/* Last uniq generated session id */ +static uint32_t s_uniq_ussd_sessiod_id = 0; + +/* Forward declaration of USSD handler for USSD MAP interface */ +static int handle_rcv_ussd_sup(struct gsm_subscriber_connection *conn, struct msgb *msg);
/* Declarations of USSD strings to be recognised */ const char USSD_TEXT_OWN_NUMBER[] = "*#100#"; @@ -52,6 +64,9 @@ int handle_rcv_ussd(struct gsm_subscriber_connection *conn, struct msgb *msg) char request_string[MAX_LEN_USSD_STRING + 1]; struct gsm48_hdr *gh;
+ if (conn->subscr->group->net->ussd_sup_client) + return handle_rcv_ussd_sup(conn, msg); + memset(&req, 0, sizeof(req)); gh = msgb_l3(msg); rc = gsm0480_decode_ss_request(gh, msgb_l3len(msg), &req); @@ -101,3 +116,164 @@ static int send_own_number(struct gsm_subscriber_connection *conn, const struct snprintf(response_string, sizeof(response_string), "Your extension is %s\r", own_number); return gsm0480_send_ussd_response(conn, response_string, req); } + + +static int ussd_sup_send_reject(struct gsm_network *conn, + uint32_t ref, uint8_t invokeid, uint8_t opcode) +{ + struct ss_request rej; + rej.message_type = GSM0480_MTYPE_RELEASE_COMPLETE; + rej.component_type = GSM0480_CTYPE_REJECT; + rej.invoke_id = invokeid; + rej.opcode = opcode; + rej.ussd_text_len = 0; + + return ussd_map_tx_message(conn, &rej, NULL, ref); +} + +/* Callback from USSD MAP interface */ +int on_ussd_response(struct gsm_network *net, uint32_t ref, struct ss_request *req, const char *extention) +{ + struct gsm_trans *trans = trans_find_by_callref(net, ref); + int rc = 0; + + switch (req->message_type) { + case GSM0480_MTYPE_REGISTER: + DEBUGP(DSS, "Network originated USSD messages isn't supported yet!\n"); + + ussd_sup_send_reject(net, ref, req->invoke_id, req->opcode); + return 0; + + case GSM0480_MTYPE_FACILITY: + case GSM0480_MTYPE_RELEASE_COMPLETE: + if (!trans) { + DEBUGP(DSS, "No session was found for ref: %d!\n", + ref); + + ussd_sup_send_reject(net, ref, req->invoke_id, req->opcode); + return 0; + } + break; + default: + DEBUGP(DSS, "Unknown message type 0x%02x\n", req->message_type); + ussd_sup_send_reject(net, ref, req->invoke_id, req->opcode); + return 0; + } + + req->invoke_id = trans->ss.invoke_id; + req->transaction_id = (trans->transaction_id << 4) ^ 0x80; + + if (req->component_type != GSM0480_CTYPE_REJECT) { + rc = gsm0480_send_ussd(trans->conn, req); + } else { + rc = gsm0480_send_ussd_reject(trans->conn, req); + } + + if (req->message_type == GSM0480_MTYPE_RELEASE_COMPLETE) { + struct gsm_subscriber_connection* conn = trans->conn; + + trans_free(trans); + msc_release_connection(conn); + } + + return rc; +} + +/* Handler function common to all mobile-originated USSDs in case if USSD MAP enabled */ +static int handle_rcv_ussd_sup(struct gsm_subscriber_connection *conn, struct msgb *msg) +{ + int rc = 0; + struct gsm48_hdr *gh = msgb_l3(msg); + struct ss_request req; + struct gsm_trans *trans = NULL; + uint8_t transaction_id = ((gh->proto_discr >> 4) ^ 0x8); /* flip */ + + if (!conn->subscr) + return -EIO; + + memset(&req, 0, sizeof(req)); + + DEBUGP(DSS, "handle ussd tid=%d: %s\n", transaction_id, msgb_hexdump(msg)); + trans = trans_find_by_id(conn, GSM48_PDISC_NC_SS, transaction_id); + + rc = gsm0480_decode_ss_request(gh, msgb_l3len(msg), &req); + if (!rc) { + DEBUGP(DSS, "Unhandled SS\n"); + if (!trans) { + goto transaction_not_found; + } + + /* don't know how to process */ + goto failed_transaction; + } + + switch (req.message_type) { + case GSM0480_MTYPE_REGISTER: + if (trans) { + /* we already have a transaction, ignore this message */ + goto release_conn; + } + + trans = trans_alloc(conn->bts->network, conn->subscr, + GSM48_PDISC_NC_SS, + transaction_id, s_uniq_ussd_sessiod_id++); + if (!trans) { + DEBUGP(DSS, "Failed to create new ussd transaction\n"); + goto transaction_not_found; + } + + trans->conn = conn; + trans->ss.invoke_id = req.invoke_id; + trans->ss.mo = 1; + trans->ss.dirty = 1; + break; + + case GSM0480_MTYPE_FACILITY: + if (!trans) { + DEBUGP(DSS, "no session found invoke_id=%d tid=%d\n", + req.invoke_id, transaction_id); + goto transaction_not_found; + } + break; + + case GSM0480_MTYPE_RELEASE_COMPLETE: + if (!trans) { + DEBUGP(DSS, "RELEASE_COMPLETE to non-existing transaction!\n"); + goto release_conn; + } + + trans_free(trans); + goto release_conn; + } + + rc = ussd_map_tx_message(conn->subscr->group->net, &req, + conn->subscr->extension, trans->callref); + if (rc) { + /* do not send reject if we failed with the message */ + trans->ss.dirty = 0; + + DEBUGP(DSS, "Unable tp send uss over sup reason: %d\n", rc); + goto failed_transaction; + } + return 0; + +failed_transaction: + trans_free(trans); + +transaction_not_found: + gsm0480_send_ussd_reject(conn, &req); + +release_conn: + msc_release_connection(conn); + return rc; +} + +void _ussd_trans_free(struct gsm_trans *trans) +{ + if (trans->ss.dirty) { + trans->ss.dirty = 0; + + ussd_sup_send_reject(trans->net, trans->callref, trans->ss.invoke_id, 0); + } +} +
From: Sergey Kostanbaev Sergey.Kostanbaev@fairwaves.co
--- openbsc/include/openbsc/gsm_data.h | 1 + openbsc/src/libmsc/vty_interface_layer3.c | 27 +++++++++++++++++++++++++++ 2 files changed, 28 insertions(+)
diff --git a/openbsc/include/openbsc/gsm_data.h b/openbsc/include/openbsc/gsm_data.h index c167c49..ffdddfb 100644 --- a/openbsc/include/openbsc/gsm_data.h +++ b/openbsc/include/openbsc/gsm_data.h @@ -244,6 +244,7 @@ struct gsm_network { struct llist_head upqueue; struct llist_head trans_list; struct bsc_api *bsc_api; + struct gprs_gsup_client *ussd_sup_client;
unsigned int num_bts; struct llist_head bts_list; diff --git a/openbsc/src/libmsc/vty_interface_layer3.c b/openbsc/src/libmsc/vty_interface_layer3.c index f49c53a..b7e1eb4 100644 --- a/openbsc/src/libmsc/vty_interface_layer3.c +++ b/openbsc/src/libmsc/vty_interface_layer3.c @@ -48,6 +48,8 @@ #include <openbsc/sms_queue.h> #include <openbsc/mncc_int.h> #include <openbsc/handover.h> +#include <openbsc/gprs_gsup_client.h> +#include <openbsc/gsm_ussd_map.h>
#include <osmocom/vty/logging.h>
@@ -987,6 +989,30 @@ DEFUN(meas_feed_scenario, meas_feed_scenario_cmd, return CMD_SUCCESS; }
+DEFUN(sup_ussd_destination, sup_ussd_destination_cmd, + "sup-ussd destination ADDR <0-65535>", + "Enable SUP USSD socket to a given address/port" "destination\n" "address or hostname\n" "port number\n") +{ + struct gsm_network *gsmnet = gsmnet_from_vty(vty); + + if (gsmnet->ussd_sup_client) { + LOGP(DMM, LOGL_FATAL, "Can't create two USSD SUP clients\n"); + vty_out(vty, "%%USSD SUP client already configured%s", VTY_NEWLINE); + return CMD_WARNING; + } + + gsmnet->ussd_sup_client = gprs_gsup_client_create( + argv[0], atoi(argv[1]), &ussd_map_read_cb); + if (!gsmnet->ussd_sup_client) { + LOGP(DMM, LOGL_FATAL, "Cannot set up USSD SUP socket\n"); + vty_out(vty, "%%Cannot set up USSD SUP socket%s", VTY_NEWLINE); + return CMD_WARNING; + } + + gsmnet->ussd_sup_client->data = gsmnet; + return CMD_SUCCESS; +} +
DEFUN(logging_fltr_imsi, logging_fltr_imsi_cmd, @@ -1124,6 +1150,7 @@ int bsc_vty_init_extra(void) install_element(NITB_NODE, &cfg_nitb_no_subscr_create_cmd); install_element(NITB_NODE, &cfg_nitb_assign_tmsi_cmd); install_element(NITB_NODE, &cfg_nitb_no_assign_tmsi_cmd); + install_element(NITB_NODE, &sup_ussd_destination_cmd);
return 0; }
From: Sergey Kostanbaev Sergey.Kostanbaev@fairwaves.co
This is USSD SUP to SIP IMS (TS 124 390) proxy converter based on sofia-sip library. It's single threaded application that uses sofia-sip event handling style for socket processing and msgb osmocom packet composer/parser.
XML is done in very simple way not using external parsing library. Language conversion is done in tricky way since XML in utf-8. First the program tries to convert it to latin1 using iconv and then to gsm 7-bit using osmcom. If it fails it converts to ucs-2 directly.
--- openbsc/src/ussd-proxy/Makefile.am | 17 + openbsc/src/ussd-proxy/ussd_proxy.c | 1265 +++++++++++++++++++++++++++++++++++ 2 files changed, 1282 insertions(+) create mode 100644 openbsc/src/ussd-proxy/Makefile.am create mode 100644 openbsc/src/ussd-proxy/ussd_proxy.c
diff --git a/openbsc/src/ussd-proxy/Makefile.am b/openbsc/src/ussd-proxy/Makefile.am new file mode 100644 index 0000000..dfb4fc6 --- /dev/null +++ b/openbsc/src/ussd-proxy/Makefile.am @@ -0,0 +1,17 @@ +AM_CPPFLAGS = $(all_includes) -I$(top_srcdir)/include -I$(top_builddir) +AM_CFLAGS=-Wall $(COVERAGE_CFLAGS) \ + $(LIBOSMOCORE_CFLAGS) $(LIBOSMOGSM_CFLAGS) $(LIBOSMOVTY_CFLAGS) \ + $(LIBOSMOCTRL_CFLAGS) $(LIBOSMOABIS_CFLAGS) \ + -I/usr/include/sofia-sip-1.12 + +AM_LDFLAGS = $(COVERAGE_LDFLAGS) + +bin_PROGRAMS = ussd-proxy + +ussd_proxy_SOURCES = \ + ussd_proxy.c ../libmsc/gsm_ussd_map_proto.c + +ussd_proxy_LDADD = \ + -lsofia-sip-ua \ + $(LIBOSMOGSM_LIBS) $(LIBOSMOVTY_LIBS) $(LIBOSMOCORE_LIBS) \ + $(LIBOSMOCTRL_LIBS) $(LIBOSMOABIS_LIBS) diff --git a/openbsc/src/ussd-proxy/ussd_proxy.c b/openbsc/src/ussd-proxy/ussd_proxy.c new file mode 100644 index 0000000..04d3826 --- /dev/null +++ b/openbsc/src/ussd-proxy/ussd_proxy.c @@ -0,0 +1,1265 @@ +#ifdef HAVE_CONFIG_H +//#include <config.h> +#endif + +#include <stddef.h> +#include <stdlib.h> +#include <string.h> +#include <stdio.h> +#include <assert.h> +#include <unistd.h> + +typedef struct context_s context_t; +#define NTA_OUTGOING_MAGIC_T context_t +#define SU_ROOT_MAGIC_T context_t +#define NUA_MAGIC_T context_t + +typedef struct operation operation_t; +#define NUA_HMAGIC_T operation_t + +#include <sofia-sip/nta.h> +#include <sofia-sip/nua.h> +#include <sofia-sip/sip_header.h> +#include <sofia-sip/sip_tag.h> +#include <sofia-sip/su_tag_io.h> +#include <sofia-sip/sl_utils.h> +#include <sofia-sip/sip_util.h> +#include <sofia-sip/auth_client.h> +#include <sofia-sip/tport_tag.h> +#include <sofia-sip/url.h> +#include <sofia-sip/su_log.h> + +#include <osmocom/abis/ipa.h> +#include <osmocom/core/application.h> +#include <osmocom/core/logging.h> +#include <osmocom/gsm/ipa.h> +#include <osmocom/gsm/protocol/ipaccess.h> +#include <osmocom/gsm/gsm0480.h> +#include <osmocom/core/linuxlist.h> + +#include <openbsc/gprs_gsup_messages.h> +#include <openbsc/gsm_ussd_map_proto.h> + +#include <iconv.h> + +typedef uint32_t sup_tcap_tid_t; + + +typedef struct isup_connection isup_connection_t; + +struct isup_connection { + context_t *ctx; + + su_socket_t isup_conn_socket; + su_wait_t isup_conn_event; + int isup_register_idx; + + /* osmocom data */ + + struct msgb *pending_msg; +}; + +struct ussd_session { + isup_connection_t *conn; + + sup_tcap_tid_t ref; + + int ms_originated; + struct ss_request rigester_msg; + + char extention[32]; +}; + +struct context_s { + su_home_t home[1]; + su_root_t *root; + + su_socket_t isup_acc_socket; + su_wait_t isup_acc_event; + + nua_t *nua; + + url_t *to_url; + url_t *self_url; + + su_timer_t *timer; + su_duration_t max_ussd_ses_duration; + + /* iconv data */ + iconv_t* utf8_to_ucs2; + iconv_t* ucs2_to_utf8; + + iconv_t* utf8_to_latin1; + iconv_t* latin1_to_utf8; + + /* Array of isup connections */ + struct isup_connection isup[1]; + + /* list of active operations */ + struct llist_head operation_list; + unsigned operation_count; + unsigned operations_max; +}; + + +/* Example of operation handle context information structure */ +struct operation +{ + struct llist_head list; + + nua_handle_t *handle; /* operation handle */ + context_t *ctx; + su_time_t tm_initiated; + + /* protocol specific sessions */ + struct ussd_session ussd; +}; + +static +int ussd_send_data(operation_t *op, int last, const char* lang, unsigned lang_len, + const char* msg, unsigned msg_len); +static +int ussd_send_data_ss(isup_connection_t *conn, struct ss_request* reply, uint32_t ref); + +static +int ussd_send_reject(isup_connection_t *conn, uint32_t ref, uint8_t invoke_id, uint8_t opcode); + +static const char* get_unknown_header(sip_t const *sip, const char *header) +{ + sip_header_t *h = (sip_header_t *)sip->sip_unknown; + for (; h; h = (sip_header_t *)h->sh_succ) { + if (strcasecmp(h->sh_unknown->un_name, header) == 0) { + return h->sh_unknown->un_value; + } + } + return NULL; +} + + +int sup_server_send(isup_connection_t *conn, struct msgb *msg) +{ + ssize_t sz; + + if (!conn) { + msgb_free(msg); + return -ENOTCONN; + } + + ipa_prepend_header_ext(msg, IPAC_PROTO_EXT_GSUP); + ipa_msg_push_header(msg, IPAC_PROTO_OSMO); + + LOGP(DLCTRL, LOGL_ERROR, + "Sending wire, will send: %s\n", msgb_hexdump(msg)); + + // FIXME ugly hack!!! + // TODO place message in send queue !!!! + sz = send(conn->isup_conn_socket, msg->data, msg->len, 0); + msgb_free(msg); + + return ((unsigned)sz == msg->len) ? 0 : -1; +} + +static int ussd_parse_xml(const char *xml, + unsigned xml_len, + const char **lang, + unsigned *lang_len, + const char **msg, + unsigned *msg_len) +{ + /* Example of parsing XML + <?xml version="1.0" encoding="UTF-8"?> + <ussd-data> + <language>en</language> + <ussd-string>Test</ussd-string> + </ussd-data> + */ + + // <ussd-data> tag + char* ussd_data_stag = strstr(xml, "<ussd-data>"); + if (ussd_data_stag == NULL) + return 0; + + char* ussd_data_etag = strstr(ussd_data_stag, "</ussd-data>"); + if (ussd_data_etag == NULL) + return 0; + + // <language> tag + char* ussd_lang_stag = strstr(ussd_data_stag, "<language>"); + if (ussd_lang_stag == NULL) + return 0; + + char* ussd_lang_etag = strstr(ussd_lang_stag, "</language>"); + if (ussd_lang_etag == NULL) + return 0; + + // <language> tag + char* ussd_ussd_stag = strstr(ussd_data_stag, "<ussd-string>"); + if (ussd_ussd_stag == NULL) + return 0; + + char* ussd_ussd_etag = strstr(ussd_ussd_stag, "</ussd-string>"); + if (ussd_ussd_etag == NULL) + return 0; + + if (ussd_ussd_etag - xml > xml_len || ussd_lang_etag - xml > xml_len) + return 0; + + *lang = ussd_lang_stag + strlen("<language>"); + *lang_len = ussd_lang_etag - *lang; + + *msg = ussd_ussd_stag + strlen("<ussd-string>"); + *msg_len = ussd_ussd_etag - *msg; + + return 1; +} + +// Operation APIs +static operation_t* operation_find_by_tid(context_t* ctx, sup_tcap_tid_t ref) +{ + operation_t* op; + llist_for_each_entry(op, &ctx->operation_list, list) { + if (op->ussd.ref == ref) + return op; + } + return NULL; +} + +static operation_t* operation_alloc(context_t* ctx) +{ + operation_t* op; + + if (ctx->operation_count >= ctx->operations_max) { + fprintf(stderr, "!!! maximum number of active session is reached: %d\n", + ctx->operation_count); + return NULL; + } + + /* create operation context information */ + op = su_zalloc(ctx->home, (sizeof *op)); + if (!op) { + return NULL; + } + + op->ctx = ctx; + op->tm_initiated = su_now(); + INIT_LLIST_HEAD(&op->list); + llist_add_tail(&op->list, &ctx->operation_list); + ctx->operation_count++; + + return op; +} + +static void operation_destroy(operation_t* op) +{ + /* release operation handle */ + nua_handle_destroy(op->handle); + op->handle = NULL; + + llist_del(&op->list); + op->ctx->operation_count--; + + fprintf(stderr, "--- operation %*.s from %s destroyed (sessions: %d)\n", + op->ussd.rigester_msg.ussd_text_len, + op->ussd.rigester_msg.ussd_text, + op->ussd.extention, + op->ctx->operation_count); + + /* release operation context information */ + su_free(op->ctx->home, op); +} + +void proxy_r_invite(int status, + char const *phrase, + nua_t *nua, + nua_magic_t *magic, + nua_handle_t *nh, + nua_hmagic_t *hmagic, + sip_t const *sip, + tagi_t tags[]) +{ + fprintf(stderr, "*** Got reply %d for INVITE\n", status); + if (status == 200) { + nua_ack(nh, TAG_END()); + } else { + printf("response to INVITE: %03d %s\n", status, phrase); + + ussd_send_reject(hmagic->ussd.conn, + hmagic->ussd.ref, + hmagic->ussd.rigester_msg.invoke_id, + hmagic->ussd.rigester_msg.opcode); + operation_destroy(hmagic); + } +} + +void proxy_i_bye(int status, + char const *phrase, + nua_t *nua, + nua_magic_t *magic, + nua_handle_t *nh, + nua_hmagic_t *hmagic, + sip_t const *sip, + tagi_t tags[]) +{ + const char* ri; + int rc; + // printf("*** call released:\n%s\n", sip->sip_payload->pl_data); + + ri = get_unknown_header(sip, "Recv-Info"); + if (ri && (strcasecmp(ri, "g.3gpp.ussd") == 0)) { + /* Parse XML */ + const char *language; + const char *msg; + unsigned language_len; + unsigned msg_len; + + if (ussd_parse_xml(sip->sip_payload->pl_data, + sip->sip_payload->pl_len, + &language, &language_len, + &msg, &msg_len)) { + printf("=== USSD (%.*s): %.*s\n", + language_len, language, + msg_len, msg); + + /* Send reply back to SUP */ + // TODO !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + rc = ussd_send_data(hmagic, 1, language, language_len, + msg, msg_len); + if (rc == 0) { + // Normal shutdown + operation_destroy(hmagic); + return; + } + + fprintf(stderr, "*** unable to send to SUP\n"); + } else { + fprintf(stderr, "*** unable to parse XML\n"); + } + } + + fprintf(stderr, "*** response BYE with %d satus is malformed, drop session\n", + status); + ussd_send_reject(hmagic->ussd.conn, + hmagic->ussd.ref, + hmagic->ussd.rigester_msg.invoke_id, + hmagic->ussd.rigester_msg.opcode); + operation_destroy(hmagic); +} + +void proxy_r_bye(int status, + char const *phrase, + nua_t *nua, + nua_magic_t *magic, + nua_handle_t *nh, + nua_hmagic_t *hmagic, + sip_t const *sip, + tagi_t tags[]) +{ + fprintf(stderr, "*** Got reply %d for BUY\n", status); + operation_destroy(hmagic); +} + +void proxy_i_error(int status, + char const *phrase, + nua_t *nua, + nua_magic_t *magic, + nua_handle_t *nh, + nua_hmagic_t *hmagic, + sip_t const *sip, + tagi_t tags[]) +{ +#if 0 + if (!hmagic) { + return; + } + + fprintf(stderr, "*** error in session with %d satus\n", + status); + ussd_send_reject(hmagic->ussd.conn, + hmagic->ussd.rigester_msg.invoke_id, + hmagic->ussd.rigester_msg.opcode); + operation_destroy(hmagic); +#endif +} + +void proxy_info(int status, + char const *phrase, + nua_t *nua, + nua_magic_t *magic, + nua_handle_t *nh, + nua_hmagic_t *hmagic, + sip_t const *sip, + tagi_t tags[], + int response) +{ + const char* ri; + int rc; + + // Normal ACK is recieved + if (response == 1 && status == 200) + return; + + ri = get_unknown_header(sip, "Recv-Info"); + if (ri && (strcasecmp(ri, "g.3gpp.ussd") == 0)) { + /* Parse XML */ + const char *language; + const char *msg; + unsigned language_len; + unsigned msg_len; + + if (ussd_parse_xml(sip->sip_payload->pl_data, + sip->sip_payload->pl_len, + &language, &language_len, + &msg, &msg_len)) { + printf("%s USSD (%.*s): %.*s\n", + (response) ? ">>>" : "<<<", + language_len, language, + msg_len, msg); + + if (hmagic == 0) { + printf("*** unknown session, ignoring"); + + // FIXME this function works only with a dialog! + nua_respond(nh, 481, "INFO with no session", TAG_END()); + return; + } + + /* Send reply back to SUP */ + // TODO !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + rc = ussd_send_data(hmagic, 0, language, language_len, + msg, msg_len); + if (rc == 0) + return; + + fprintf(stderr, "*** unable to send to SUP in INFO\n"); + } else { + fprintf(stderr, "*** unable to parse XML in INFO\n"); + } + } + + fprintf(stderr, "*** %s INFO with %d satus is malformed, drop session\n", + response ? "response" : "request", + status); + ussd_send_reject(hmagic->ussd.conn, + hmagic->ussd.ref, + hmagic->ussd.rigester_msg.invoke_id, + hmagic->ussd.rigester_msg.opcode); + operation_destroy(hmagic); +} + +int ussd_create_xml_latin1(context_t* ctx, + char *content, size_t max_len, + const char* inbuf_latin1, int buf_len) +{ + const char *language = "en"; + char tmpbuf_utf8[2*MAX_LEN_USSD_STRING]; + unsigned tmpbuf_utf8_len; + + char* inbuf = (char*)inbuf_latin1; + size_t inleft = buf_len; + char* outbuf = tmpbuf_utf8; + size_t outleft = sizeof(tmpbuf_utf8); + size_t s; + + s = iconv(ctx->latin1_to_utf8, &inbuf, &inleft, &outbuf, &outleft); + if (s == (size_t)-1) { + LOGP(DLCTRL, LOGL_ERROR, "Unable to encode latin1 into utf8\n"); + return 0; + } + + tmpbuf_utf8_len = outbuf - tmpbuf_utf8; + + int content_len = snprintf(content, max_len, + "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" + "<ussd-data>\n" + "<language>%s</language>\n" + "<ussd-string>%.*s</ussd-string>\n" + "</ussd-data>", + language, + tmpbuf_utf8_len, tmpbuf_utf8); + if (content_len > max_len) { + content[max_len - 1] = 0; + return 0; + } + return 1; +} + +static int decode_to_latin1(char* outbuf, unsigned size, + const uint8_t* msg, unsigned msg_len, uint8_t lang) +{ + if (lang == 0x0f) { + return gsm_7bit_decode_n_ussd(outbuf, size, msg, msg_len); + } else { + LOGP(DLCTRL, LOGL_ERROR, "Unknown language: 0x%02x\n", lang); + return 0; + } +} + +/* URL_RESERVED_CHARS in sofia is not strict enough as in RFC3986 */ +#define RFC3986_RESERVED_CHARS "!*'();:@&=+$,/?#[]" + +int ussd_session_open_mo(operation_t *op, + isup_connection_t *conn, + struct ss_request* ss, + uint32_t ref, + const char* extention) +{ + char content[1024]; + char decoded[MAX_LEN_USSD_STRING + 1]; + char escaped_to[512]; + context_t* ctx = op->ctx; + sip_to_t *to = NULL; + sip_to_t *from = NULL; + url_t to_url, from_url; + char* to_url_str; + char* from_url_str; + + int decoded_len; + + op->ussd.ref = ref; + op->ussd.conn = conn; + op->ussd.ms_originated = 1; + op->ussd.rigester_msg = *ss; + strncpy(op->ussd.extention, extention, sizeof(op->ussd.extention)); + + decoded_len = decode_to_latin1(decoded, MAX_LEN_USSD_STRING, + op->ussd.rigester_msg.ussd_text, + op->ussd.rigester_msg.ussd_text_len, + op->ussd.rigester_msg.ussd_text_language); + if (decoded_len <= 0) { + goto failed_to_parse_xml; + } + decoded[decoded_len] = 0; + + if (!ussd_create_xml_latin1(ctx, content, sizeof(content), + decoded, decoded_len)) { + goto failed_to_parse_xml; + } + + + /* Destination address */ + url_escape(escaped_to, decoded, RFC3986_RESERVED_CHARS); + to_url = *ctx->to_url; + to_url.url_user = escaped_to; + to_url_str = url_as_string(ctx->home, &to_url); + if (to_url_str == NULL) { + goto failed_create_handle; + } + + to = sip_to_create(ctx->home, (url_string_t *)to_url_str); + su_free(ctx->home, to_url_str); + if (!to) { + goto failed_create_handle; + } + + /* Source address */ + from_url = *ctx->self_url; + from_url.url_user = extention; + from_url_str = url_as_string(ctx->home, &from_url); + if (from_url_str == NULL) { + goto failed_create_handle; + } + + from = sip_from_create(ctx->home, (url_string_t *)from_url_str); + su_free(ctx->home, from_url_str); + if (!to) { + goto failed_create_handle; + } + + /* create operation handle */ + op->handle = nua_handle(ctx->nua, + op, + SIPTAG_TO(to), + SIPTAG_FROM(from), + NUTAG_M_USERNAME(extention), + TAG_END()); + + su_free(ctx->home, from); + su_free(ctx->home, to); + from = NULL; + to = NULL; + + if (op->handle == NULL) { + goto failed_create_handle; + } + + nua_invite(op->handle, + SIPTAG_UNKNOWN_STR("Recv-Info: g.3gpp.ussd"), + SIPTAG_CONTENT_TYPE_STR("application/vnd.3gpp.ussd+xml"), + SIPTAG_PAYLOAD_STR(content), + TAG_END()); + return 0; + +failed_create_handle: + if (from != NULL) + su_free(ctx->home, from); + if (to != NULL) + su_free(ctx->home, to); +failed_to_parse_xml: + fprintf(stderr, "*** open_ussd_session failed!\n"); + return -1; +} + +int ussd_session_facility(operation_t *op, + struct ss_request* ss, + const char* extention) +{ + char content[1024]; + char decoded[MAX_LEN_USSD_STRING + 1]; + int decoded_len; + + decoded_len = decode_to_latin1(decoded, MAX_LEN_USSD_STRING, + op->ussd.rigester_msg.ussd_text, + op->ussd.rigester_msg.ussd_text_len, + op->ussd.rigester_msg.ussd_text_language); + if (decoded_len <= 0) { + return -1; + } + decoded[decoded_len] = 0; + + if (!ussd_create_xml_latin1(op->ctx, content, sizeof(content), + decoded, decoded_len)) { + return -1; + } + + nua_info(op->handle, + /* other tags as needed ... */ + SIPTAG_CONTENT_TYPE_STR("application/vnd.3gpp.ussd+xml"), + SIPTAG_UNKNOWN_STR("Recv-Info: g.3gpp.ussd"), + SIPTAG_PAYLOAD_STR(content), + TAG_END()); + + return 0; +} + +void context_callback(nua_event_t event, + int status, + char const *phrase, + nua_t *nua, + nua_magic_t *magic, + nua_handle_t *nh, + nua_hmagic_t *hmagic, + sip_t const *sip, + tagi_t tags[]) +{ + fprintf(stderr, "$$$ got event %d: status: %d (%s) : %p\n", event, status, phrase, hmagic); + + switch (event) { + case nua_i_error: + proxy_i_error(status, phrase, nua, magic, nh, hmagic, sip, tags); + break; + + case nua_i_info: + proxy_info(status, phrase, nua, magic, nh, hmagic, sip, tags, 0); + break; + + case nua_r_info: + proxy_info(status, phrase, nua, magic, nh, hmagic, sip, tags, 1); + break; + + case nua_i_bye: + proxy_i_bye(status, phrase, nua, magic, nh, hmagic, sip, tags); + break; + + case nua_i_invite: + //app_i_invite(status, phrase, nua, magic, nh, hmagic, sip, tags); + break; + + case nua_r_invite: + proxy_r_invite(status, phrase, nua, magic, nh, hmagic, sip, tags); + break; + + case nua_r_bye: + proxy_r_bye(status, phrase, nua, magic, nh, hmagic, sip, tags); + break; + + default: + /* unknown event -> print out error message */ + if (status > 100) { + printf("unknown event %d: %03d %s\n", + event, + status, + phrase); + } else { + printf("unknown event %d\n", event); + } + tl_print(stdout, "", tags); + break; + } +} + +static int rx_sup_uss_message(isup_connection_t *sup_conn, const uint8_t* data, size_t len) +{ + char extention[32] = {0}; + struct ss_request ss; + uint32_t ref; + operation_t* op; + int rc; + context_t *ctx = sup_conn->ctx; + memset(&ss, 0, sizeof(ss)); + + if (rx_uss_message_parse(data, len, &ss, &ref, extention, sizeof(extention))) { + LOGP(DLCTRL, LOGL_ERROR, "Can't parse uss message\n"); + goto err_send_reject; + } + + LOGP(DLCTRL, LOGL_ERROR, "Got ref=%d mtype=0x%02x invoke_id=0x%02x opcode=0x%02x component_type=0x%02x text=%s\n", ref, + ss.message_type, ss.invoke_id, ss.opcode, ss.component_type, ss.ussd_text); + + switch (ss.message_type) { + case GSM0480_MTYPE_REGISTER: + if (ss.component_type != GSM0480_CTYPE_INVOKE) { + LOGP(DLCTRL, LOGL_ERROR, "Non-INVOKE component type in REGISTER: 0x%02x\n", ss.component_type); + goto err_send_reject; + } + if (ss.opcode != GSM0480_OP_CODE_PROCESS_USS_REQ) { + LOGP(DLCTRL, LOGL_ERROR, "Don't know hot to handle this SS opcode: 0x%02x\n", ss.opcode); + goto err_send_reject; + } + /* Create new session */ + op = operation_alloc(ctx); + if (op == NULL) { + LOGP(DLCTRL, LOGL_ERROR, "Unable to allocate new session\n"); + goto err_send_reject; + } + LOGP(DLCTRL, LOGL_ERROR, "New session %.*s from %s, active: %d\n", + ss.ussd_text_len, + ss.ussd_text, + extention, + ctx->operation_count); + + rc = ussd_session_open_mo(op, sup_conn, &ss, ref, extention); + if (rc < 0) { + operation_destroy(op); + goto err_send_reject; + } + break; + + case GSM0480_MTYPE_FACILITY: + //Only MS-originated Menu session is supported, so we ignore INVOKE here + if (ss.component_type != GSM0480_CTYPE_RETURN_RESULT && + ss.component_type != GSM0480_CTYPE_RETURN_ERROR && + ss.component_type != GSM0480_CTYPE_REJECT) { + LOGP(DLCTRL, LOGL_ERROR, "Non-{RESULT/RETURN_ERROR/REJECT} component type in FACILITY: 0x%02x\n", ss.component_type); + goto err_send_reject; + } + // ///////////////////////////////////////////////// + // TODO handle RETURN_ERROR/REJECT + if (ss.component_type != GSM0480_CTYPE_RETURN_RESULT) { + LOGP(DLCTRL, LOGL_ERROR, "Component type in FACILITY: 0x%02x is not implemented yet\n", ss.component_type); + goto err_send_reject; + } + if (ss.opcode != GSM0480_OP_CODE_USS_REQUEST) { + LOGP(DLCTRL, LOGL_ERROR, "Don't know hot to handle this SS opcode: 0x%02x\n", ss.opcode); + goto err_send_reject; + } + op = operation_find_by_tid(ctx, ref); + if (op == NULL) { + LOGP(DLCTRL, LOGL_ERROR, "No active session with tid=%d were found\n", + ss.invoke_id); + goto err_send_reject; + } + + // TODO check result!! MO/MT error handling + rc = ussd_session_facility(op, &ss, extention); + if (rc < 0) { + operation_destroy(op); + goto err_send_reject; + } + break; + + case GSM0480_MTYPE_RELEASE_COMPLETE: + op = operation_find_by_tid(ctx, ref); + if (op == NULL) { + LOGP(DLCTRL, LOGL_ERROR, "No active session with tid=%d were found for RELEASE_COMPLETE\n", + ss.invoke_id); + return 0; + } + + nua_bye(op->handle, TAG_END()); + break; + + default: + LOGP(DLCTRL, LOGL_ERROR, "Unknown message type 0x%02x\n", ss.message_type); + goto err_send_reject; + } + + return 0; + +err_send_reject: + ussd_send_reject(sup_conn, ref, ss.invoke_id, ss.opcode); + return -1; +} + +int ussd_send_reject(isup_connection_t *conn, uint32_t ref, uint8_t invoke_id, uint8_t opcode) +{ + struct ss_request error_ss; + + memset(&error_ss, 0, sizeof(error_ss)); + error_ss.message_type = GSM0480_MTYPE_RELEASE_COMPLETE; + error_ss.component_type = GSM0480_CTYPE_REJECT; + error_ss.invoke_id = invoke_id; + error_ss.opcode = opcode; + + return ussd_send_data_ss(conn, &error_ss, ref); +} + +int ussd_send_data_ss(isup_connection_t *conn, struct ss_request* reply, uint32_t ref) +{ + struct msgb *outmsg = msgb_alloc_headroom(4000, 64, __func__); + subscr_uss_message(outmsg, + reply, + NULL, + ref); + + LOGP(DLCTRL, LOGL_ERROR, + "Sending USS, will send: %s\n", msgb_hexdump(outmsg)); + + return sup_server_send(conn, outmsg); +} + +int ussd_send_data(operation_t *op, int last, const char* lang, unsigned lang_len, + const char* msg, unsigned msg_len) +{ + struct ss_request ss; + memset(&ss, 0, sizeof(ss)); + + // TODO handle language + if (msg == NULL) { + ss.message_type = GSM0480_MTYPE_RELEASE_COMPLETE; + ss.component_type = GSM0480_CTYPE_REJECT; + ss.opcode = op->ussd.rigester_msg.opcode; + } else if (last) { + ss.message_type = GSM0480_MTYPE_RELEASE_COMPLETE; + ss.component_type = GSM0480_CTYPE_RETURN_RESULT; + ss.opcode = op->ussd.rigester_msg.opcode; + } else { + ss.message_type = GSM0480_MTYPE_FACILITY; + ss.component_type = (op->ussd.ms_originated) ? GSM0480_CTYPE_INVOKE + : GSM0480_CTYPE_RETURN_RESULT; + ss.opcode = GSM0480_OP_CODE_USS_REQUEST; + } + + ss.invoke_id = op->ussd.rigester_msg.invoke_id; + + if (msg) { + char tmpbuf[MAX_LEN_USSD_STRING + 1]; + + char* inbuf = (char*)msg; + size_t inleft = msg_len; + char* outbuf = (char*)tmpbuf; + size_t outleft = sizeof(tmpbuf); + size_t s; + + // First of all try latin1 + s = iconv(op->ctx->utf8_to_latin1, + &inbuf, &inleft, + &outbuf, &outleft); + if (s == (size_t)-1) { + outbuf = (char*)ss.ussd_text; + outleft = MAX_LEN_USSD_STRING; + + s = iconv(op->ctx->utf8_to_ucs2, + &inbuf, &inleft, + &outbuf, &outleft); + if (s == (size_t)-1) { + perror("can't convert string from utf8"); + } + // UCS-2 encoding + ss.ussd_text_language = 0x48; + ss.ussd_text_len = (uint8_t*)outbuf - ss.ussd_text; + + } else { + int outlen; + + // Set null-termination + outbuf[0] = 0; + gsm_7bit_encode_n_ussd(ss.ussd_text, + MAX_LEN_USSD_STRING, outbuf, &outlen); + ss.ussd_text_len = outlen; + ss.ussd_text_language = 0x0f; + } + } else { + ss.ussd_text_len = 0; + ss.ussd_text_language = 0x0f; + ss.ussd_text[0] = 0; + } + + return ussd_send_data_ss(op->ussd.conn, &ss, op->ussd.ref); +} + +static void timer_function(su_root_magic_t *magic, + su_timer_t *t, + su_timer_arg_t *arg) +{ + context_t *cli = (context_t*)arg; + su_time_t n = su_now(); + + operation_t *op, *tmp; + llist_for_each_entry_safe(op, tmp, &cli->operation_list, list) { + su_duration_t lasts = su_duration(n, op->tm_initiated); + if (lasts > cli->max_ussd_ses_duration) { + fprintf(stderr, "!!! session %.*s from %s lasted %ld ms, more than thresold %ld ms, destroying\n", + op->ussd.rigester_msg.ussd_text_len, + op->ussd.rigester_msg.ussd_text, + op->ussd.extention, + lasts, + cli->max_ussd_ses_duration); + + + ussd_send_reject(op->ussd.conn, + op->ussd.ref, + op->ussd.rigester_msg.invoke_id, + op->ussd.rigester_msg.opcode); + operation_destroy(op); + } + } +} + +static int isup_handle_connection(context_t *cli, su_wait_t *w, void *p) +{ + int rc; + isup_connection_t *conn = (isup_connection_t*)p; + + int events = su_wait_events(w, conn->isup_conn_socket); + printf("*** connection; event=0x%x\n", events); + + if (events & (SU_WAIT_ERR | SU_WAIT_HUP)) { + printf("*** connection destroyed\n"); + goto err; + } else if (events & SU_WAIT_IN) { + /* Incoming data */ + + struct ipaccess_head *iph; + struct msgb *msg = NULL; + int ret = ipa_msg_recv_buffered(conn->isup_conn_socket, &msg, &conn->pending_msg); + if (ret <= 0) { + if (ret == -EAGAIN) + return 0; + if (ret == 0) + LOGP(DLCTRL, LOGL_INFO, "The control connection was closed\n"); + else + LOGP(DLCTRL, LOGL_ERROR, "Failed to parse ip access message: %d\n", ret); + + goto err; + } + + iph = (struct ipaccess_head *) msg->data; + switch (iph->proto) + { + case IPAC_PROTO_IPACCESS: + if (msg->l2h[0] == IPAC_MSGT_PING) { + printf("*** got PING\n"); + msg->l2h[0] = IPAC_MSGT_PONG; + send(conn->isup_conn_socket, msg->data, ntohs(iph->len) + sizeof(struct ipaccess_head), 0); + msgb_free(msg); + conn->pending_msg = NULL; + return 0; + } + + LOGP(DLCTRL, LOGL_ERROR, "Unknown IPAC_PROTO_IPACCESS msg 0x%x\n", msg->l2h[0]); + goto err; + case IPAC_PROTO_OSMO: + // TODO callback + if (msg->l2h[1] == GPRS_GSUP_MSGT_USSD_MAP) { + LOGP(DLCTRL, LOGL_ERROR, + "Receive USS: %s\n", msgb_hexdump(msg)); + + rc = rx_sup_uss_message(conn, &msg->l2h[1], msgb_l2len(msg) - 1); + if (rc < 0) { + /* TODO raise reject !!!!!!! */ + /* release complete */ + } + + msgb_free(msg); + conn->pending_msg = NULL; + return 0; + } + + /* TODO: handle gprs_gsup_decode() for other types */ + + LOGP(DLCTRL, LOGL_ERROR, "Unknown IPAC_PROTO_OSMO GPRS_GSUP_MSGT_* 0x%x\n", msg->l2h[1]); + msgb_free(msg); + conn->pending_msg = NULL; + goto err; + default: + LOGP(DLCTRL, LOGL_ERROR, "Protocol mismatch. We got 0x%x\n", iph->proto); + goto err; + } + } + + return 0; + +err: + close(conn->isup_conn_socket); + conn->isup_conn_socket = INVALID_SOCKET; + + su_wait_destroy(w); + + msgb_free(conn->pending_msg); + conn->pending_msg = NULL; + //su_root_deregister(cli, cli->isup_register_idx); + return 0; +} + +static int isup_handle_accept(context_t *cli, su_wait_t *w, void *p) +{ + su_sockaddr_t aaddr; + su_socket_t connection; + socklen_t len = sizeof(aaddr); + int rc; + + connection = accept(cli->isup_acc_socket, &aaddr.su_sa, &len); + if (connection == INVALID_SOCKET) { + perror("can't accept isup socket"); + return 0; + } + + printf("*** accepted from %s:%d\n", + inet_ntoa(aaddr.su_sin.sin_addr), + ntohs(aaddr.su_sin.sin_port)); + + /* TODO manage isup connection list, but just now use the single connection */ + isup_connection_t *conn = cli->isup; + if (conn->isup_conn_socket != INVALID_SOCKET) { + fprintf(stderr, "--- Can't accept, there's another connection\n"); + su_close(connection); + return 0; + } + + conn->ctx = cli; + conn->isup_conn_socket = connection; + conn->pending_msg = NULL; + + su_wait_init(&conn->isup_conn_event); + rc = su_wait_create(&conn->isup_conn_event, + conn->isup_conn_socket, + SU_WAIT_IN | /*SU_WAIT_OUT | */ SU_WAIT_HUP | SU_WAIT_ERR); + + conn->isup_register_idx = su_root_register(cli->root, + &conn->isup_conn_event, + isup_handle_connection, + conn, + 0); + return 0; +} + +#define DIPA_USSD_PROXY 0 + +struct log_info_cat ipa_proxy_test_cat[] = { + [DIPA_USSD_PROXY] = { + .name = "DIPA_USSD_PROXY", + .description = "USSD_PROXY", + .color = "\033[1;35m", + .enabled = 1, + .loglevel = LOGL_DEBUG, + }, +}; + +const struct log_info ipa_proxy_test_log_info = { + .filter_fn = NULL, + .cat = ipa_proxy_test_cat, + .num_cat = ARRAY_SIZE(ipa_proxy_test_cat), +}; + + +static void Usage(char* progname) +{ + fprintf(stderr, "Usage:\n" + "%s [options]\n" + "Options\n" + " -p <port> TCP port to listen incoming SUP connection\n" + " (default: 8184)\n" + " -t <url> Destination SIP URL (default: sip:127.0.0.1:5060)\n" + " -u <url> User agent SIP URL (default: sip:127.0.0.1:5090)\n" + " -x <url> Proxy SIP URL (default: <none>)\n" + " -T Force using TCP instead trying UDP\n" + " -D <secs> Maximum period of open USSD session (default: 90)\n" + " -o <sessions> Maximum number of concurrent USSD sessions\n" + " (default: 200)\n" + " -l <0-9> sip sofia loglevel, 0 - none; 9 - max\n" + , progname); +} + +int main(int argc, char *argv[]) +{ + su_home_t *home; + context_t context[1] = {{{SU_HOME_INIT(context)}}}; + su_sockaddr_t listen_addr; + int rc; + int sup_port = 8184; + const char* to_str = "sip:127.0.0.1:5060"; + const char* url_str = "sip:127.0.0.1:5090"; + const char* proxy_str = NULL; + int force_tcp = 0; + int max_ussd_ses_secs = 90; + int max_op_limit = 200; + int sip_loglevel = 1; + int c; + + while ((c = getopt (argc, argv, "x:p:t:u:D:To:l:L7?")) != -1) { + switch (c) + { + case 'x': + proxy_str = optarg; + break; + case 'p': + sup_port = atoi(optarg); + break; + case 't': + to_str = optarg; + break; + case 'u': + url_str = optarg; + break; + case 'T': + force_tcp = 1; + break; + case 'D': + max_ussd_ses_secs = atoi(optarg); + break; + case 'o': + max_op_limit = atoi(optarg); + break; + case 'l': + sip_loglevel = atoi(optarg); + break; + case 'L': + fprintf(stderr, " -L is now obsolete, ignored\n"); + break; + case '7': + fprintf(stderr, " -7 is now obsolete, ignored\n"); + break; + case '?': + default: + Usage(argv[0]); + return 2; + } + } + + osmo_init_logging(&ipa_proxy_test_log_info); + + su_init(); + su_home_init(home = context->home); + + context->root = su_root_create(context); + + su_log_set_level(NULL, sip_loglevel); + + /* Disable threading */ + su_root_threading(context->root, 0); + + if (!context->root) { + fprintf(stderr, "Unable to initialize sip-sofia context\n"); + return 1; + } + + context->utf8_to_latin1=iconv_open("iso8859-1", "utf-8"); + context->latin1_to_utf8=iconv_open("utf-8", "iso8859-1"); + context->utf8_to_ucs2=iconv_open("utf-16be", "utf-8"); + context->ucs2_to_utf8=iconv_open("utf-8", "utf-16be"); + + if (context->utf8_to_ucs2 == NULL || context->ucs2_to_utf8 == NULL || + context->utf8_to_latin1 == NULL || context->latin1_to_utf8 == NULL) { + fprintf(stderr, "Unable to initialize iconv\n"); + return 1; + } + + context->isup_acc_socket = su_socket(AF_INET, SOCK_STREAM, 0); + if (context->isup_acc_socket == INVALID_SOCKET) { + perror("unable to create socket\n"); + return 1; + } + su_setblocking(context->isup_acc_socket, 0); + su_setreuseaddr(context->isup_acc_socket, 1); + + context->isup->isup_conn_socket = INVALID_SOCKET; + + memset(&listen_addr, 0, sizeof(listen_addr)); + listen_addr.su_sin.sin_family = AF_INET; + listen_addr.su_sin.sin_addr.s_addr = INADDR_ANY; + listen_addr.su_sin.sin_port = htons(sup_port); + + rc = bind(context->isup_acc_socket, &listen_addr.su_sa, sizeof(listen_addr.su_sin)); + if (rc < 0) { + perror("cannot bind socket\n"); + return 2; + } + + rc = listen(context->isup_acc_socket, 1); + if (rc < 0) { + perror("cannot bind socket\n"); + return 2; + } + + su_wait_init(&context->isup_acc_event); + su_wait_create(&context->isup_acc_event, context->isup_acc_socket, SU_WAIT_ACCEPT); + su_root_register(context->root, + &context->isup_acc_event, + isup_handle_accept, + NULL, + 0); + + context->to_url = url_make(home, to_str); + context->self_url = url_make(home, url_str); + + if (context->to_url == NULL) { + fprintf(stderr, "Unable to parse destination URL\n"); + return 1; + } + if (context->self_url == NULL) { + fprintf(stderr, "Unable to parse our (source) URL\n"); + return 1; + } + + context->nua = nua_create(context->root, + context_callback, + context, + NUTAG_URL(url_str), + NUTAG_ENABLEINVITE(1), + NUTAG_AUTOALERT(1), + NUTAG_SESSION_TIMER(0), + NUTAG_AUTOANSWER(0), + NUTAG_MEDIA_ENABLE(0), + NUTAG_ALLOW("INVITE, ACK, BYE, CANCEL, INFO"), + TAG_NULL()); + if (context->nua == NULL) { + fprintf(stderr, "Unable to initialize sip-sofia nua\n"); + return 1; + } + + + if (proxy_str) { + nua_set_params(context->nua, + NUTAG_PROXY(proxy_str), + TAG_NULL()); + } + + if (force_tcp) { + nua_set_params(context->nua, + NTATAG_UDP_MTU(10), + TAG_NULL()); + } + + INIT_LLIST_HEAD(&context->operation_list); + context->operation_count = 0; + context->operations_max = max_op_limit; + + su_timer_t* tm = su_timer_create(su_root_task(context->root), 2000); + if (tm == NULL) { + fprintf(stderr, "Unable to initialize sip-sofia timer\n"); + return 1; + } + rc = su_timer_run(tm, timer_function, context); + if (rc < 0) { + fprintf(stderr, "Unable to start sip-sofia timer\n"); + return 1; + } + context->timer = tm; + context->max_ussd_ses_duration = max_ussd_ses_secs * 1000l; + + su_root_run(context->root); + nua_destroy(context->nua); + + return 0; +} +
From: Sergey Kostanbaev Sergey.Kostanbaev@fairwaves.co
--- openbsc/configure.ac | 12 ++++++++++++ openbsc/src/Makefile.am | 2 +- openbsc/src/ussd-proxy/Makefile.am | 2 ++ 3 files changed, 15 insertions(+), 1 deletion(-)
diff --git a/openbsc/configure.ac b/openbsc/configure.ac index fc30b5e..5489133 100644 --- a/openbsc/configure.ac +++ b/openbsc/configure.ac @@ -172,6 +172,17 @@ AC_MSG_CHECKING([whether to enable VTY/CTRL tests]) AC_MSG_RESULT([$enable_ext_tests]) AM_CONDITIONAL(ENABLE_EXT_TESTS, test "x$enable_ext_tests" = "xyes")
+# Enable/disable ussd_proxy utility +AC_ARG_ENABLE([ussd_proxy], [AS_HELP_STRING([--enable-ussd-proxy], [Build the USSD MAP SUP to SIP proxy])], + [osmo_ac_build_ussd_proxy="$enableval"],[osmo_ac_build_ussd_proxy="no"]) +if test "$osmo_ac_build_ussd_proxy" = "yes" ; then + PKG_CHECK_MODULES(LIBSOFIA_SIP_UA, sofia-sip-ua >= 1.10) + AC_DEFINE(BUILD_USSD_PROXY, 1, [Define if we want to build ussd_proxy]) +fi +AM_CONDITIONAL(BUILD_USSD_PROXY, test "x$osmo_ac_build_ussd_proxy" = "xyes") +AC_SUBST(osmo_ac_build_smpp) + + dnl Generate the output AM_CONFIG_HEADER(bscconfig.h)
@@ -193,6 +204,7 @@ AC_OUTPUT( src/ipaccess/Makefile src/utils/Makefile src/gprs/Makefile + src/ussd-proxy/Makefile tests/Makefile tests/atlocal tests/gsm0408/Makefile diff --git a/openbsc/src/Makefile.am b/openbsc/src/Makefile.am index 6f6174e..f12ed54 100644 --- a/openbsc/src/Makefile.am +++ b/openbsc/src/Makefile.am @@ -2,7 +2,7 @@ AM_CPPFLAGS = $(all_includes) -I$(top_srcdir)/include -I$(top_builddir) AM_CFLAGS=-Wall $(LIBOSMOCORE_CFLAGS) $(LIBOSMOGSM_CFLAGS) $(LIBOSMOGSM_CFLAGS) $(LIBOSMOVTY_CFLAGS) $(COVERAGE_CFLAGS) AM_LDFLAGS = $(LIBOSMOCORE_LIBS) $(LIBOSMOGSM_LIBS) $(COVERAGE_LDFLAGS)
-SUBDIRS = libcommon libmgcp libbsc libmsc libtrau libfilter osmo-nitb osmo-bsc_mgcp utils ipaccess gprs +SUBDIRS = libcommon libmgcp libbsc libmsc libtrau libfilter osmo-nitb osmo-bsc_mgcp utils ipaccess gprs ussd-proxy
# Conditional modules if BUILD_NAT diff --git a/openbsc/src/ussd-proxy/Makefile.am b/openbsc/src/ussd-proxy/Makefile.am index dfb4fc6..68f3a88 100644 --- a/openbsc/src/ussd-proxy/Makefile.am +++ b/openbsc/src/ussd-proxy/Makefile.am @@ -1,3 +1,4 @@ +if BUILD_USSD_PROXY AM_CPPFLAGS = $(all_includes) -I$(top_srcdir)/include -I$(top_builddir) AM_CFLAGS=-Wall $(COVERAGE_CFLAGS) \ $(LIBOSMOCORE_CFLAGS) $(LIBOSMOGSM_CFLAGS) $(LIBOSMOVTY_CFLAGS) \ @@ -15,3 +16,4 @@ ussd_proxy_LDADD = \ -lsofia-sip-ua \ $(LIBOSMOGSM_LIBS) $(LIBOSMOVTY_LIBS) $(LIBOSMOCORE_LIBS) \ $(LIBOSMOCTRL_LIBS) $(LIBOSMOABIS_LIBS) +endif \ No newline at end of file
From: Sergey Kostanbaev sergey.kostanbaev@gmail.com
USSD SUP based socket depends on gprs SUP files
--- openbsc/src/libmsc/Makefile.am | 3 +++ 1 file changed, 3 insertions(+)
diff --git a/openbsc/src/libmsc/Makefile.am b/openbsc/src/libmsc/Makefile.am index 616ed89..ce3f6a4 100644 --- a/openbsc/src/libmsc/Makefile.am +++ b/openbsc/src/libmsc/Makefile.am @@ -10,6 +10,9 @@ libmsc_a_SOURCES = auth.c \ db.c \ gsm_04_08.c gsm_04_11.c gsm_04_11_helper.c \ gsm_04_80.c \ + ../gprs/gsm_04_08_gprs.c \ + ../gprs/gprs_utils.c \ + ../gprs/gprs_gsup_messages.c ../gprs/gprs_gsup_client.c \ gsm_subscriber.c \ mncc.c mncc_builtin.c mncc_sock.c \ rrlp.c \
On 25 Nov 2015, at 12:03, Sergey.Kostanbaev sergey.kostanbaev@gmail.com wrote:
From: Sergey Kostanbaev sergey.kostanbaev@gmail.com
This series of patches add support for external USSD processing via simplified MAP-like protocol over SUP socket. As an example of a such external application simple USSD to SIP proxy is provided (it targets TS 124 390)
Have you considered using SMPP for USSD? At first this looks more natural than a custom protocol like SUP.
holger
On 25 Nov 2015, at 13:22, Holger Freyther holger@freyther.de wrote:
Have you considered using SMPP for USSD? At first this looks more natural than a custom protocol like SUP.
The other part I notice when going through the patches are the following:
* Fixes, features without a unit test is very difficult to sell * SUP has a protocol description. If the protocol is changed the description should be updated. * If gprs sup client is used in non GPRS context we either split the protocol up or at least rename and move these files around.
If you have questions of any of the above points I am happy to answer them.
holger
On Wed, Nov 25, 2015 at 3:31 PM, Holger Freyther holger@freyther.de wrote:
On 25 Nov 2015, at 13:22, Holger Freyther holger@freyther.de wrote:
Have you considered using SMPP for USSD? At first this looks more natural than a custom protocol like SUP.
The other part I notice when going through the patches are the following:
- Fixes, features without a unit test is very difficult to sell
I agree that having a unit test is a good sign. But I'm not really familiar with the project rules.
I can make unit tests for packet parsing/composing (SS and MAP-like level). But I have no idea how to make a unit test for e.g. USSD transaction based on gsm_trans or more complex things. I'd be glad to hear your thoughts.
- SUP has a protocol description. If the protocol is changed the
description should be updated.
I think I don't get this. Which protocol description are you talking about?
* If gprs sup client is used in non GPRS context we either split the
protocol up or at least rename and move these files around.
I intentionally left this part as is to get a feedback how it's possible solve. So you suggest to separate abstract socket operations away from GPRS, right?
If you have questions of any of the above points I am happy to answer them.
holger
Yes, initially I thought about using SMPP for USSD. But it looked really complicated since only few external SMPP libraries have support of USSD and most of that few are in C#/Java/etc. Moreover I didn't have any working software for USSD over SMPP to test interoperability with.
So I decided to use GPRS SUP socket as a simple transport to send MAP-like messages. Workflow of this SUP is very simple and basically it forwards SS messages with little modifications.
On Wed, Nov 25, 2015 at 3:22 PM, Holger Freyther holger@freyther.de wrote:
On 25 Nov 2015, at 12:03, Sergey.Kostanbaev sergey.kostanbaev@gmail.com
wrote:
From: Sergey Kostanbaev sergey.kostanbaev@gmail.com
This series of patches add support for external USSD processing via simplified MAP-like protocol over SUP socket. As an example of a such external application simple USSD to SIP proxy is provided (it targets TS 124 390)
Have you considered using SMPP for USSD? At first this looks more natural than a custom protocol like SUP.
holger
On 25 Nov 2015, at 14:07, sergey kostanbaev sergey.kostanbaev@gmail.com wrote:
Yes, initially I thought about using SMPP for USSD. But it looked really complicated since only few external SMPP libraries have support of USSD and most of that few are in C#/Java/etc. Moreover I didn't have any working software for USSD over SMPP to test interoperability with.
Hmm. We try to follow standard protocol when they exist. E.g. with "GSUP" the decision was that we have a good SS7 MAP stack (with high amount of tests and Q.787 conformance) and doing that in C at the time didn't look like an economic choice.
Do you remember how USSD is mapped to SMPP?
So I decided to use GPRS SUP socket as a simple transport to send MAP-like messages. Workflow of this SUP is very simple and basically it forwards SS messages with little modifications.
But why GPRS SUP then? E.g. it would be enough for you to use OAP (that just adds a very simple way for authentication)? Which of the existing GSUP messages do you use?
holger
On Wed, Nov 25, 2015 at 4:12 PM, Holger Freyther holger@freyther.de wrote:
On 25 Nov 2015, at 14:07, sergey kostanbaev sergey.kostanbaev@gmail.com
wrote:
Yes, initially I thought about using SMPP for USSD. But it looked really
complicated since only few external SMPP libraries have support of USSD and most of that few are in C#/Java/etc. Moreover I didn't have any working software for USSD over SMPP to test interoperability with.
Hmm. We try to follow standard protocol when they exist. E.g. with "GSUP" the decision was that we have a good SS7 MAP stack (with high amount of tests and Q.787 conformance) and doing that in C at the time didn't look like an economic choice.
Do you remember how USSD is mapped to SMPP?
Yeah I have a description but wasn't able to test it in a real
environment. I need to dig in my archives. I'll let you know.
So I decided to use GPRS SUP socket as a simple transport to send
MAP-like messages. Workflow of this SUP is very simple and basically it forwards SS messages with little modifications.
But why GPRS SUP then? E.g. it would be enough for you to use OAP (that just adds a very simple way for authentication)? Which of the existing GSUP messages do you use?
Well may be I wrongly used GPRS SUP as a description. I used none of GPRS
SUP messages. I used the ability to send messages over "TCP" (TCP + header) socket. Such code of a socket was in GSUP. I added GPRS_GSUP_MSGT_USSD_MAP constant to not pollute and distinguish that messages. It can b done on a raw socket, but I just reused GSUP socket wrapper. May be this was confusing.
holger
On 25 Nov 2015, at 14:37, sergey kostanbaev sergey.kostanbaev@gmail.com wrote:
Yeah I have a description but wasn't able to test it in a real environment. I need to dig in my archives. I'll let you know.
That would be appreciated and help us making the decision if we want to go with SMPP or a custom protocol. When there were customer requests at sysmocom we had always quoted SMPP work.
I know it is unfortunate that you have made the SMPP vs. custom protocol question and spent a lot of work on making it work but for us it is important to know the reasons, e.g. what would client support require for libsmpp34, is there no client support because the protocol is too bad or that not much special needs to be handled.
So I decided to use GPRS SUP socket as a simple transport to send MAP-like messages. Workflow of this SUP is very simple and basically it forwards SS messages with little modifications.
In the long run we should move the re-connect handling into libosmo-abis then.
kind regards and thank you for your contribution holger
Hi all,
On Wed, Nov 25, 2015 at 03:20:16PM +0100, Holger Freyther wrote:
That would be appreciated and help us making the decision if we want to go with SMPP or a custom protocol. When there were customer requests at sysmocom we had always quoted SMPP work.
I think I could never figure out how to model the session-oriented properties of USSD on top of SMPP. I might have missed something and never really investigaed it all the way, but it seemed that SMPP can only be used for 'single shot' USSD transactions, rether than an USSD session with multiple messages.
Regards, Harald
On Sat, Nov 28, 2015 at 12:54 PM, Harald Welte laforge@gnumonks.org wrote:
Hi all,
On Wed, Nov 25, 2015 at 03:20:16PM +0100, Holger Freyther wrote:
That would be appreciated and help us making the decision if we want to go with SMPP or a custom protocol. When there were customer requests at sysmocom we had always quoted SMPP work.
I think I could never figure out how to model the session-oriented properties of USSD on top of SMPP. I might have missed something and never really investigaed it all the way, but it seemed that SMPP can only be used for 'single shot' USSD transactions, rether than an USSD session with multiple messages.
To add to this discussion - one of the reasons we chose SUP over SMPP is that we're going to export other SS services as well.
Hi Holger,
On Wed, Nov 25, 2015 at 8:12 AM, Holger Freyther holger@freyther.de wrote:
On 25 Nov 2015, at 14:07, sergey kostanbaev sergey.kostanbaev@gmail.com wrote:
Yes, initially I thought about using SMPP for USSD. But it looked really complicated since only few external SMPP libraries have support of USSD and most of that few are in C#/Java/etc. Moreover I didn't have any working software for USSD over SMPP to test interoperability with.
Hmm. We try to follow standard protocol when they exist.
Actually, our end goal is to support TS 124 390 or at least get close enough to it. When we discussed integration of SIP into OsmoNITB there was a strong opposition from you and Harald, so we were looking for the best way to export required functionality from OsmoNITB to avoid adding SIP into the OsmoNITB itself. Within this logic SMPP just adds overhead and extra complexity without offering any benefits.