Harald Welte has uploaded this change for review. ( https://gerrit.osmocom.org/13653
Change subject: RAN_Emulation: Add RANAP support ...................................................................... RAN_Emulation: Add RANAP support So far, RAN_Emulation only handled BSSAP and hence could be used to emulate BSCs towards the MSC. Let's extend it with RANAP support so we can also emulate RNCs towards the MSC. We try to share as much code and logic as possible betweeb the two. Related: OS#2856, OS#2857 Change-Id: Ie79bda764162e5c5a42608bde5c5f486ea531f33 --- M library/RAN_Adapter.ttcnpp M library/RAN_Emulation.ttcnpp M library/ranap/RANAP_Templates.ttcn 3 files changed, 418 insertions(+), 10 deletions(-) git pull ssh://gerrit.osmocom.org:29418/osmo-ttcn3-hacks refs/changes/53/13653/1 diff --git a/library/RAN_Adapter.ttcnpp b/library/RAN_Adapter.ttcnpp index 43b4988..a97d148 100644 --- a/library/RAN_Adapter.ttcnpp +++ b/library/RAN_Adapter.ttcnpp @@ -43,7 +43,8 @@ type enumerated RAN_Transport { BSSAP_TRANSPORT_AoIP, /* 3GPP AoIP: SCCP over M3UA over SCTP */ BSSAP_TRANSPORT_SCCPlite_SERVER, /* SCCPlite: SCCP over IPA over TCP */ - BSSAP_TRANSPORT_SCCPlite_CLIENT /* SCCPlite: SCCP over IPA over TCP */ + BSSAP_TRANSPORT_SCCPlite_CLIENT, /* SCCPlite: SCCP over IPA over TCP */ + RANAP_TRANSPORT_IuCS /* 3GPP IuCS: SCCP over M3UA over SCTP */ }; type record RAN_Configuration { @@ -88,8 +89,7 @@ ba.vc_RAN := RAN_Emulation_CT.create(id & "-RAN"); } select (cfg.transport) { -#ifdef RAN_EMULATION_BSSAP - case (BSSAP_TRANSPORT_AoIP) { + case (BSSAP_TRANSPORT_AoIP, RANAP_TRANSPORT_IuCS) { ba.vc_M3UA := M3UA_CT.create(id & "-M3UA"); map(ba.vc_M3UA:SCTP_PORT, system:sctp); /* connect MTP3 service provider (M3UA) to lower side of SCCP */ @@ -129,7 +129,6 @@ ba.vc_WAIT.done; disconnect(ba.vc_IPA:IPA_SP_PORT, ba.vc_WAIT:IPA_SP_PORT); } -#endif case else { setverdict(fail, "Unsuppored RAN_Transport"); mtc.stop; @@ -141,16 +140,22 @@ T.start; //T.timeout; log("Connecting BSSMAP Emulation to SCCP_SP_PORT and starting emulation"); -#if RAN_EMULATION_BSSAP /* connect BSSNAP component to upper side of SCCP */ - connect(ba.vc_RAN:BSSAP, ba.vc_SCCP:SCCP_SP_PORT); + if (cfg.transport == RANAP_TRANSPORT_IuCS) { +#ifdef RAN_EMULATION_RANAP + ops.protocol := RAN_PROTOCOL_RANAP + connect(ba.vc_RAN:RANAP, ba.vc_SCCP:SCCP_SP_PORT); #endif + } else { +#ifdef RAN_EMULATION_BSSAP + connect(ba.vc_RAN:BSSAP, ba.vc_SCCP:SCCP_SP_PORT); +#endif + } if (cfg.transport == BSSAP_TRANSPORT_SCCPlite_SERVER or cfg.transport == BSSAP_TRANSPORT_SCCPlite_CLIENT) { /* connect IPA MGCP port with BSSMAP MGCP port */ connect(ba.vc_IPA:IPA_MGCP_PORT, ba.vc_RAN:MGCP); } - /* start the BSSMAP emulation */ ba.vc_RAN.start(RAN_Emulation.main(valueof(ops), "")); } diff --git a/library/RAN_Emulation.ttcnpp b/library/RAN_Emulation.ttcnpp index 21dbea9..b826982 100644 --- a/library/RAN_Emulation.ttcnpp +++ b/library/RAN_Emulation.ttcnpp @@ -48,6 +48,14 @@ import from MGCP_Templates all; #endif +#ifdef RAN_EMULATION_RANAP +import from RANAP_CodecPort all; +import from RANAP_PDU_Descriptions all; +import from RANAP_Constants all; +import from RANAP_IEs all; +import from RANAP_Templates all; +#endif + /* General "base class" component definition, of which specific implementations * derive themselves by means of the "extends" feature */ type component RAN_ConnHdlr { @@ -110,6 +118,11 @@ /* Client requests us to create SCCP Connection */ BSSAP_Conn_Req, #endif +#ifdef RAN_EMULATION_RANAP + RANAP_PDU, + /* Client requests us to create SCCP Connection */ + RANAP_Conn_Req, +#endif #ifdef RAN_EMULATION_MGCP /* MGCP, only used for IPA SCCPlite (MGCP in IPA mux) */ MgcpCommand, MgcpResponse, @@ -147,6 +160,9 @@ #ifdef RAN_EMULATION_BSSAP port BSSAP_CODEC_PT BSSAP; #endif +#ifdef RAN_EMULATION_RANAP + port RANAP_CODEC_PT RANAP; +#endif /* BSSAP port to the per-connection clients */ port RAN_Conn_PT CLIENT; #ifdef RAN_EMULATION_MGCP @@ -487,10 +503,137 @@ } #endif +#ifdef RAN_EMULATION_RANAP +type record RANAP_Conn_Req { + SCCP_PAR_Address addr_peer, + SCCP_PAR_Address addr_own, + RANAP_PDU ranap +} +template (value) RANAP_Conn_Req ts_RANAP_Conn_Req(SCCP_PAR_Address peer, SCCP_PAR_Address own, RANAP_PDU ranap) := { + addr_peer := peer, + addr_own := own, + ranap := ranap +}; + +private function fake_dlci_from_sapi(template (omit) SAPI sapi) return template (omit) OCT1 +{ + if (istemplatekind(sapi, "omit")) { + return omit; + } else if (valueof(sapi) == sapi_3) { + return '03'O; + } + return '00'O; +} + +private function f_handle_userData_RANAP(RAN_ConnHdlr client, RANAP_PDU ranap) +runs on RAN_Emulation_CT { + /* decode + send decoded RANAP to client */ + var template (omit) octetstring l3 := f_ranap_extract_l3(ranap); + if (istemplatekind(ranap, "omit")) { + CLIENT.send(ranap) to client; + } else { + var template (omit) SAPI sapi := f_ranap_extract_sapi(ranap); + var template (omit) OCT1 dlci := fake_dlci_from_sapi(sapi); + if (g_ran_ops.role_ms) { + /* we are the MS, so any message to us must be MT */ + var PDU_DTAP_MT mt := { + dlci := omit, + dtap := dec_PDU_ML3_NW_MS(valueof(l3)) + }; + if (isvalue(dlci)) { + mt.dlci := valueof(dlci) + } + CLIENT.send(mt) to client; + } else { + /* we are the Network, so any message to us must be MO */ + var PDU_DTAP_MO mo := { + dlci := omit, + dtap := dec_PDU_ML3_MS_NW(valueof(l3)) + }; + if (isvalue(dlci)) { + mo.dlci := valueof(dlci) + } + CLIENT.send(mo) to client; + } + } +} + +/* call-back type, to be provided by specific implementation; called when new SCCP connection + * arrives */ +type function RanapCreateCallback(RANAP_N_CONNECT_ind conn_ind, charstring id) +runs on RAN_Emulation_CT return RAN_ConnHdlr; + +type function RanapUnitdataCallback(RANAP_PDU ranap) +runs on RAN_Emulation_CT return template RANAP_PDU; + +private function CommonRanapUnitdataCallback(RANAP_PDU ranap) +runs on RAN_Emulation_CT return template RANAP_PDU { + if (match(ranap, tr_RANAP_Paging(?, ?))) { + var RAN_ConnHdlr client := null; + /* extract IMSI and (if present) TMSI */ + var IMSI imsi := ranap.initiatingMessage.value_.paging.protocolIEs[1].value_.permanentNAS_UE_ID.iMSI; + var template OCT4 tmsi := omit; + if (lengthof(ranap.initiatingMessage.value_.paging.protocolIEs) > 2 and + ranap.initiatingMessage.value_.paging.protocolIEs[2].id == id_TemporaryUE_ID) { + var TemporaryUE_ID ue_id; + ue_id := ranap.initiatingMessage.value_.paging.protocolIEs[2].value_.temporaryUE_ID; + if (ischosen(ue_id.tMSI)) { + tmsi := ue_id.tMSI; + } else { + tmsi := ue_id.p_TMSI; + } + } + client := f_imsi_table_find(oct2hex(imsi), tmsi); + if (isvalue(client)) { + log("CommonRanapUnitdataCallback: IMSI/TMSI found in table, dispatching to ", + client); + CLIENT.send(ranap) to client; + return omit; + } + log("CommonRanapUnitdataCallback: IMSI/TMSI not found in table"); + } else { + log("CommonRanapUnitdataCallback: Not a paging message"); + } + + /* ELSE: handle in user callback */ + return g_ran_ops.ranap_unitdata_cb.apply(ranap); +} + +private function f_ranap_l3_is_rr(RANAP_PDU ranap) return boolean { + var template (omit) SAPI sapi; + var template octetstring l3 := f_ranap_extract_l3(ranap); + return f_L3_is_rr(l3); +} + +function f_ranap_reset(SCCP_PAR_Address peer, SCCP_PAR_Address own) runs on RAN_Emulation_CT { + timer T := 5.0; + var CN_DomainIndicator dom; + if (g_ran_ops.ps_domain) { + dom := ps_domain; + } else { + dom := cs_domain; + } + + RANAP.send(ts_RANAP_UNITDATA_req(peer, own, ts_RANAP_Reset(ts_RanapCause_om_intervention, dom))); + T.start; + alt { + [] RANAP.receive(tr_RANAP_UNITDATA_ind(own, peer, tr_RANAP_ResetAck)) { + log("Received RESET-ACK in response to RESET, we're ready to go!"); + } + [] as_reset_ack(); + [] RANAP.receive { repeat }; + [] T.timeout { + setverdict(fail, "Timeout waiting for RESET-ACK after sending RESET"); + mtc.stop; + } + } +} +#endif type enumerated RanProtocol { - RAN_PROTOCOL_BSSAP + RAN_PROTOCOL_BSSAP, + RAN_PROTOCOL_RANAP } type record RanOps { @@ -498,6 +641,11 @@ BssmapCreateCallback create_cb optional, BssmapUnitdataCallback unitdata_cb optional, #endif +#ifdef RAN_EMULATION_RANAP + RanapCreateCallback ranap_create_cb optional, + RanapUnitdataCallback ranap_unitdata_cb optional, + boolean ps_domain, +#endif boolean decode_dtap, boolean role_ms, RanProtocol protocol, @@ -551,6 +699,9 @@ #ifdef RAN_EMULATION_BSSAP var BSSAP_N_UNITDATA_ind ud_ind; #endif +#ifdef RAN_EMULATION_RANAP + var RANAP_N_UNITDATA_ind rud_ind; +#endif #ifdef RAN_EMULATION_BSSAP [] BSSAP.receive(tr_BSSAP_UNITDATA_ind(?, ?, tr_BSSMAP_Reset)) -> value ud_ind { log("Respoding to inbound RESET with RESET-ACK"); @@ -559,6 +710,15 @@ repeat; } #endif +#ifdef RAN_EMULATION_RANAP + [] RANAP.receive(tr_RANAP_UNITDATA_ind(?, ?, tr_RANAP_Reset)) -> value rud_ind { + log("Respoding to inbound IuRESET with IuRESET-ACK"); + var CN_DomainIndicator dom; + dom := rud_ind.userData.initiatingMessage.value_.Reset.protocolIEs[1].value_.cN_DomainIndicator; + RANAP.send(ts_RANAP_UNITDATA_req(rud_ind.callingAddress, rud_ind.calledAddress, + ts_RANAP_ResetAck(dom))); + } +#endif } @@ -666,7 +826,116 @@ } #else - [false] CLIENT.receive(false) {} + [false] CLIENT.receive {} +#endif +} + +private altstep as_main_ranap() runs on RAN_Emulation_CT { +#ifdef RAN_EMULATION_RANAP + var RANAP_N_UNITDATA_ind rud_ind; + var RANAP_N_CONNECT_ind rconn_ind; + var RANAP_N_CONNECT_cfm rconn_cfm; + var RANAP_N_DATA_ind rdata_ind; + var RANAP_N_DISCONNECT_ind rdisc_ind; + var RANAP_Conn_Req creq; + var RANAP_PDU ranap; + var RAN_ConnHdlr vc_conn; + + /* SCCP -> Client: UNIT-DATA (connectionless SCCP) from a BSC */ + [] RANAP.receive(RANAP_N_UNITDATA_ind:?) -> value rud_ind { + /* Connectionless Procedures like RESET */ + var template RANAP_PDU resp; + resp := CommonRanapUnitdataCallback(rud_ind.userData); + if (isvalue(resp)) { + RANAP.send(ts_RANAP_UNITDATA_req(rud_ind.callingAddress, + rud_ind.calledAddress, resp)); + } + } + /* SCCP -> Client: new connection from BSC */ + [] RANAP.receive(RANAP_N_CONNECT_ind:?) -> value rconn_ind { + vc_conn := g_ran_ops.ranap_create_cb.apply(rconn_ind, g_ran_id); + /* store mapping between client components and SCCP connectionId */ + f_conn_table_add(vc_conn, rconn_ind.connectionId); + /* handle user payload */ + f_handle_userData_RANAP(vc_conn, rconn_ind.userData); + /* confirm connection establishment */ + RANAP.send(ts_RANAP_CONNECT_res(rconn_ind.connectionId, omit)); + } + /* SCCP -> Client: connection-oriented data in existing connection */ + [] RANAP.receive(RANAP_N_DATA_ind:?) -> value rdata_ind { + vc_conn := f_comp_by_conn_id(rdata_ind.connectionId); + if (ispresent(rdata_ind.userData)) { + f_handle_userData_RANAP(vc_conn, rdata_ind.userData); + } + } + /* SCCP -> Client: disconnect of an existing connection */ + [] RANAP.receive(RANAP_N_DISCONNECT_ind:?) -> value rdisc_ind { + vc_conn := f_comp_by_conn_id(rdisc_ind.connectionId); + if (ispresent(rdisc_ind.userData)) { + f_handle_userData_RANAP(vc_conn, rdisc_ind.userData); + } + /* notify client about termination */ + var RAN_Conn_Prim prim := MSC_CONN_PRIM_DISC_IND; + CLIENT.send(prim) to vc_conn; + f_conn_table_del(rdisc_ind.connectionId); + /* TOOD: return confirm to other side? */ + } + /* SCCP -> Client: connection confirm for outbound connection */ + [] RANAP.receive(RANAP_N_CONNECT_cfm:?) -> value rconn_cfm { + vc_conn := f_comp_by_conn_id(rconn_cfm.connectionId); + var RAN_Conn_Prim prim := MSC_CONN_PRIM_CONF_IND; + CLIENT.send(prim) to vc_conn; + /* handle user payload */ + if (ispresent(rconn_cfm.userData)) { + f_handle_userData_RANAP(vc_conn, rconn_cfm.userData); + } + } + + [] CLIENT.receive(RANAP_PDU:?) -> value ranap sender vc_conn { + var integer conn_id := f_conn_id_by_comp(vc_conn); + /* send it to dispatcher */ + RANAP.send(ts_RANAP_DATA_req(conn_id, ranap)); + } + + /* Disconnect request client -> SCCP */ + [] CLIENT.receive(RAN_Conn_Prim:MSC_CONN_PRIM_DISC_REQ) -> sender vc_conn { + var integer conn_id := f_conn_id_by_comp(vc_conn); + RANAP.send(ts_RANAP_DISC_req(conn_id, 0)); + f_conn_table_del(conn_id); + } + + /* BSSAP from client -> SCCP */ + [] CLIENT.receive(RANAP_Conn_Req:?) -> value creq sender vc_conn { + var integer conn_id; + /* send to dispatcher */ + + if (f_comp_known(vc_conn) == false) { + /* unknown client, create new connection */ + conn_id := f_gen_conn_id(); + + /* store mapping between client components and SCCP connectionId */ + f_conn_table_add(vc_conn, conn_id); + + RANAP.send(ts_RANAP_CONNECT_req(creq.addr_peer, creq.addr_own, conn_id, + creq.ranap)); + } else { + /* known client, send via existing connection */ + conn_id := f_conn_id_by_comp(vc_conn); + RANAP.send(ts_RANAP_DATA_req(conn_id, creq.ranap)); + } + + /* InitialL3 contains RR (PAG RESP) or MM (CM SRV REQ), we must increment + * counter only on MM/CC/SS, but not on RR */ + if (g_ran_ops.role_ms and not f_ranap_l3_is_rr(creq.ranap)) { + /* we have just sent the first MM message, increment the counter */ + var integer idx := f_idx_by_comp(vc_conn); + ConnectionTable[idx].n_sd[0] := 1; + log("patch: N(SD) for ConnIdx ", idx, " set to 1"); + } + } + +#else + [false] CLIENT.receive {} #endif } @@ -730,6 +999,18 @@ BSSAP.send(ts_BSSAP_DATA_req(sccp_conn_id, bssap)); } #endif +#ifdef RAN_EMULATION_RANAP + case (RAN_PROTOCOL_RANAP) { + var RANAP_PDU ranap; + if (false /* SAPI */) { + var RANAP_IEs.SAPI sapi := sapi_0; + ranap := valueof(ts_RANAP_DirectTransferSAPI(l3_enc, sapi)); + } else { + ranap := valueof(ts_RANAP_DirectTransfer(l3_enc)); + } + RANAP.send(ts_RANAP_DATA_req(sccp_conn_id, ranap)); + } +#endif } } @@ -742,7 +1023,18 @@ if (isvalue(ops.sccp_addr_peer) and isvalue(ops.sccp_addr_local)) { f_sleep(1.0); /* HACK to wait for M3UA/ASP to be ACTIVE */ - f_bssap_reset(ops.sccp_addr_peer, ops.sccp_addr_local); + select (g_ran_ops.protocol) { +#ifdef RAN_EMULATION_BSSAP + case (RAN_PROTOCOL_BSSAP) { + f_bssap_reset(ops.sccp_addr_peer, ops.sccp_addr_local); + } +#endif +#ifdef RAN_EMULATION_RANAP + case (RAN_PROTOCOL_RANAP) { + f_ranap_reset(ops.sccp_addr_peer, ops.sccp_addr_local); + } +#endif + } } while (true) { @@ -756,6 +1048,7 @@ alt { [g_ran_ops.protocol == RAN_PROTOCOL_BSSAP] as_main_bssap(); + [g_ran_ops.protocol == RAN_PROTOCOL_RANAP] as_main_ranap(); [g_ran_ops.role_ms] CLIENT.receive(PDU_DTAP_MO:?) -> value dtap_mo sender vc_conn { var integer idx := f_idx_by_comp(vc_conn); @@ -822,6 +1115,7 @@ inout RAN_register, RAN_register_imsi; } with { extension "internal" }; +#ifdef RAN_EMULATION_BSSAP /* CreateCallback that can be used as create_cb and will use the expectation table */ function ExpectedCreateCallback(BSSAP_N_CONNECT_ind conn_ind, charstring id) runs on RAN_Emulation_CT return RAN_ConnHdlr { @@ -854,6 +1148,42 @@ mtc.stop; return ret; } +#endif + +#ifdef RAN_EMULATION_RANAP +/* CreateCallback that can be used as create_cb and will use the expectation table */ +function RanapExpectedCreateCallback(RANAP_N_CONNECT_ind conn_ind, charstring id) +runs on RAN_Emulation_CT return RAN_ConnHdlr { + var RAN_ConnHdlr ret := null; + var template (omit) octetstring l3_info; + var integer i; + + l3_info := f_ranap_extract_l3(conn_ind.userData); + if (istemplatekind(l3_info, "omit")) { + setverdict(fail, "N-CONNECT.ind without NAS payload"); + mtc.stop; + return ret; + } + + for (i := 0; i < sizeof(ExpectTable); i:= i+1) { + if (not ispresent(ExpectTable[i].l3_payload)) { + continue; + } + if (valueof(l3_info) == ExpectTable[i].l3_payload) { + ret := ExpectTable[i].vc_conn; + /* release this entry to be used again */ + ExpectTable[i].l3_payload := omit; + ExpectTable[i].vc_conn := null; + log("Found Expect[", i, "] for ", l3_info, " handled at ", ret); + /* return the component reference */ + return ret; + } + } + setverdict(fail, "Couldn't find Expect for incoming connection ", conn_ind); + mtc.stop; + return ret; +} +#endif private function f_create_expect(octetstring l3, RAN_ConnHdlr hdlr) runs on RAN_Emulation_CT { diff --git a/library/ranap/RANAP_Templates.ttcn b/library/ranap/RANAP_Templates.ttcn index c50440c..4d18e1c 100644 --- a/library/ranap/RANAP_Templates.ttcn +++ b/library/ranap/RANAP_Templates.ttcn @@ -9,6 +9,8 @@ import from RANAP_PDU_Contents all; import from RANAP_PDU_Descriptions all; +template (value) Cause ts_RanapCause_om_intervention := { misc := 113 }; + /***************************************************************************************************** * Reset *****************************************************************************************************/ @@ -668,6 +670,37 @@ } } } +template RANAP_PDU +tr_RANAP_Paging(template CN_DomainIndicator dom, template IMSI imsi, + template Paging.protocolExtensions exts := *) := { + initiatingMessage := { + procedureCode := id_Paging, + criticality := ignore, + value_ := { + paging := { + protocolIEs := { + { + id := id_CN_DomainIndicator, + criticality := ignore, + value_ := { + cN_DomainIndicator := dom + } + }, { + id := id_PermanentNAS_UE_ID, + criticality := ignore, + value_ := { + permanentNAS_UE_ID := { + iMSI := imsi + } + } + }, * + }, + protocolExtensions := exts + } + } + } +} + /***************************************************************************************************** @@ -1308,7 +1341,47 @@ * *****************************************************************************************************/ +/* extract the L3 (NAS) from a given RANAP message */ +function f_ranap_extract_l3(RANAP_PDU ranap) return template (omit) octetstring +{ + var integer i; + if (match(ranap, tr_RANAP_initialUE_CS(?, ?, ?, ?, ?)) or + match(ranap, tr_RANAP_initialUE_PS(?, ?, ?, ?, ?, ?)) ) { + var InitialUE_Message.protocolIEs ies := ranap.initiatingMessage.value_.initialUE_Message.protocolIEs; + for (i := 0; i < lengthof(ies); i := i+1) { + if (ies[i].id == id_NAS_PDU) { + return ies[i].value_.nAS_PDU; + } + } + } else if (match(ranap, tr_RANAP_DirectTransfer(?, ?))) { + var DirectTransfer.protocolIEs ies := ranap.initiatingMessage.value_.directTransfer.protocolIEs; + for (i := 0; i < lengthof(ies); i := i+1) { + if (ies[i].id == id_NAS_PDU) { + return ies[i].value_.nAS_PDU; + } + } + } else { + /* relocationInformation not supported yet*/ + } + return omit; +} + +function f_ranap_extract_sapi(RANAP_PDU ranap) return template (omit) SAPI +{ + var integer i; + + /* InitialUE message has no SAPI */ + if (match(ranap, tr_RANAP_DirectTransfer(?, ?))) { + var DirectTransfer.protocolIEs ies := ranap.initiatingMessage.value_.directTransfer.protocolIEs; + for (i := 0; i < lengthof(ies); i := i+1) { + if (ies[i].id == id_SAPI) { + return ies[i].value_.sAPI; + } + } + } + return omit; +} } -- To view, visit https://gerrit.osmocom.org/13653 To unsubscribe, or for help writing mail filters, visit https://gerrit.osmocom.org/settings Gerrit-Project: osmo-ttcn3-hacks Gerrit-Branch: master Gerrit-MessageType: newchange Gerrit-Change-Id: Ie79bda764162e5c5a42608bde5c5f486ea531f33 Gerrit-Change-Number: 13653 Gerrit-PatchSet: 1 Gerrit-Owner: Harald Welte <lafo...@gnumonks.org>