/* OsmoS1GW (S1AP Gateway) ConnHdlr * * (C) 2024 by sysmocom - s.f.m.c. GmbH * Author: Vadim Yanitskiy * * All rights reserved. * * Released under the terms of GNU General Public License, Version 2 or * (at your option) any later version. * * SPDX-License-Identifier: GPL-2.0-or-later */ module S1GW_ConnHdlr { import from General_Types all; import from Osmocom_Types all; import from Native_Functions all; import from IPL4asp_Types all; import from Misc_Helpers all; import from Mutex all; import from PFCP_Types all; import from PFCP_Emulation all; import from PFCP_Templates all; import from PFCP_CodecPort all; import from S1AP_CodecPort all; import from S1AP_CodecPort_CtrlFunct all; import from S1AP_Types all; import from S1AP_Templates all; import from S1AP_PDU_Descriptions all; import from S1AP_IEs all; import from S1AP_PDU_Contents all; import from S1AP_Constants all; import from SCTP_Templates all; import from StatsD_Types all; import from StatsD_Checker all; import from S1AP_Server all; type component ConnHdlr extends MutexCT, S1APSRV_ConnHdlr, PFCP_ConnHdlr, StatsD_ConnHdlr { var ConnHdlrPars g_pars; port S1AP_CODEC_PT S1AP_ENB; var ConnectionId g_s1ap_conn_id := -1; }; type record of ConnHdlr ConnHdlrList; type record ConnHdlrPars { integer idx, Global_ENB_ID genb_id, charstring statsd_prefix, charstring pfcp_loc_addr, charstring pfcp_rem_addr, MME_UE_S1AP_ID mme_ue_id, ERabList erabs }; template Global_ENB_ID ts_Global_ENB_ID(integer enb_id := 0, OCT3 plmn_id := '00f110'O) := { pLMNidentity := plmn_id, eNB_ID := { macroENB_ID := int2bit(enb_id, 20) }, iE_Extensions := omit } type function void_fn(charstring id) runs on ConnHdlr; function f_ConnHdlr_init(void_fn fn, charstring id, ConnHdlrPars pars) runs on ConnHdlr { g_pars := pars; fn.apply(id); } function f_ConnHdlr_s1ap_connect(charstring local_addr, charstring remote_addr) runs on ConnHdlr { var Result res; timer T; map(self:S1AP_ENB, system:S1AP_CODEC_PT); /* initiate SCTP connection establishment */ res := S1AP_CodecPort_CtrlFunct.f_IPL4_connect(S1AP_ENB, remote_addr, 36412, local_addr, 0, -1, { sctp := c_SctpTuple_S1AP }); if (not ispresent(res.connId)) { Misc_Helpers.f_shutdown(__BFILE__, __LINE__, fail, "Could not create an S1AP socket, check your configuration"); } g_s1ap_conn_id := res.connId; /* wait for the establishment confirmation */ T.start(2.0); alt { [] S1AP_ENB.receive(tr_SctpAssocChange(SCTP_COMM_UP, g_s1ap_conn_id)) { log("eNB connection established"); } [] S1AP_ENB.receive(PortEvent:{sctpEvent := ?}) { repeat; } [] T.timeout { Misc_Helpers.f_shutdown(__BFILE__, __LINE__, fail, "eNB connection establishment timeout"); } } } function f_ConnHdlr_s1ap_disconnect() runs on ConnHdlr { var Result res; S1AP_CodecPort_CtrlFunct.f_IPL4_close(S1AP_ENB, g_s1ap_conn_id, { sctp := c_SctpTuple_S1AP }); g_s1ap_conn_id := -1; unmap(self:S1AP_ENB, system:S1AP_CODEC_PT); S1AP_CONN.receive(S1APSRV_Event:S1APSRV_EVENT_CONN_DOWN); log("eNB connection closed"); } function f_ConnHdlr_s1ap_expect_shutdown() runs on ConnHdlr { S1AP_ENB.receive(tr_SctpShutDownEvent(g_s1ap_conn_id)); S1AP_ENB.receive(tr_SctpAssocChange(SCTP_SHUTDOWN_COMP, g_s1ap_conn_id)); S1AP_ENB.receive(PortEvent:{connClosed := ?}); } function f_ConnHdlr_tx_s1ap_from_enb(template (value) S1AP_PDU pdu) runs on ConnHdlr { S1AP_ENB.send(t_S1AP_Send(g_s1ap_conn_id, pdu)); } function f_ConnHdlr_tx_s1ap_from_mme(template (value) S1AP_PDU pdu) runs on ConnHdlr { S1AP_CONN.send(pdu); } function f_ConnHdlr_rx_s1ap_from_enb(out S1AP_PDU pdu, template (present) S1AP_PDU tr_pdu := ?, float Tval := 3.0) runs on ConnHdlr { timer T := Tval; T.start; alt { [] S1AP_CONN.receive(tr_pdu) -> value pdu { setverdict(pass); T.stop; } [] S1AP_CONN.receive(S1AP_PDU:?) -> value pdu { Misc_Helpers.f_shutdown(__BFILE__, __LINE__, fail, log2str("Rx unexpected S1AP PDU from eNB: ", pdu)); } [] T.timeout { Misc_Helpers.f_shutdown(__BFILE__, __LINE__, fail, log2str("Timeout waiting for S1AP PDU from eNB: ", tr_pdu)); } } } function f_ConnHdlr_rx_s1ap_from_mme(out S1AP_PDU pdu, template (present) S1AP_PDU tr_pdu := ?, float Tval := 3.0) runs on ConnHdlr { var S1AP_RecvFrom recv; timer T := Tval; T.start; alt { [] S1AP_ENB.receive(t_S1AP_RecvFrom(tr_pdu)) -> value recv { pdu := recv.msg; setverdict(pass); T.stop; } [] S1AP_ENB.receive(t_S1AP_RecvFrom(?)) -> value recv { pdu := recv.msg; Misc_Helpers.f_shutdown(__BFILE__, __LINE__, fail, log2str("Rx unexpected S1AP PDU from MME: ", pdu)); } [] T.timeout { Misc_Helpers.f_shutdown(__BFILE__, __LINE__, fail, log2str("Timeout waiting for S1AP PDU from MME: ", tr_pdu)); } } } function f_ConnHdlr_s1ap_setup(Global_ENB_ID genb_id, float Tval := 5.0) runs on ConnHdlr { var S1AP_PDU pdu; timer T; var SupportedTAs supported_tas_dummy := { { tAC := '0000'O, broadcastPLMNs := { '00f000'O }, iE_Extensions := omit } }; f_ConnHdlr_tx_s1ap_from_enb(ts_S1AP_SetupReq(genb_id, supported_tas_dummy, v32)); T.start(Tval); alt { [] S1AP_CONN.receive(S1APSRV_Event:S1APSRV_EVENT_CONN_UP) { repeat; } [] S1AP_CONN.receive(tr_S1AP_SetupReq) { var template (value) PLMNidentity plmn_id := '00f110'O; var template (value) MME_Group_ID mme_group_id := '0011'O; var template (value) MME_Code mme_code := '55'O; var template (value) ServedGUMMEIsItem gummei := ts_S1AP_ServedGUMMEIsItem( { plmn_id }, { mme_group_id }, { mme_code } ); f_ConnHdlr_tx_s1ap_from_mme(ts_S1AP_SetupResp({ gummei }, 1)); f_ConnHdlr_rx_s1ap_from_mme(pdu, tr_S1AP_SetupResp({ gummei }, 1)); T.stop; } [] S1AP_CONN.receive(S1AP_PDU:?) -> value pdu { Misc_Helpers.f_shutdown(__BFILE__, __LINE__, fail, log2str("Rx unexpected S1AP PDU: ", pdu)); } [] T.timeout { Misc_Helpers.f_shutdown(__BFILE__, __LINE__, fail, log2str("Timeout waiting for S1AP SetupReq, idx = ", g_pars.idx)); } } } private function f_ConnHdlr_pfcp_assoc_setup() runs on ConnHdlr { var PDU_PFCP rx; f_PFCPEM_subscribe_bcast(); /* ask PFCPEM to route all PDUs to us */ rx := f_ConnHdlr_pfcp_expect(tr_PFCP_Assoc_Setup_Req, Tval := 10.0); f_PFCPEM_unsubscribe_bcast(); /* ask PFCPEM to *not* route all PDUs to us */ PFCP.send(ts_PFCP_Assoc_Setup_Resp(rx.sequence_number, ts_PFCP_Node_ID_fqdn("\07osmocom\03org"), ts_PFCP_Cause(REQUEST_ACCEPTED), f_PFCPEM_get_recovery_timestamp())); } function f_ConnHdlr_pfcp_expect(template (present) PDU_PFCP exp_rx := ?, float Tval := 2.0) runs on ConnHdlr return PDU_PFCP { var PDU_PFCP pdu; timer T; T.start(Tval); alt { [] PFCP.receive(exp_rx) -> value pdu { T.stop; } [] PFCP.receive(PDU_PFCP:?) -> value pdu { Misc_Helpers.f_shutdown(__BFILE__, __LINE__, fail, log2str("Got an unexpected PFCP PDU: ", pdu)); } [] T.timeout { Misc_Helpers.f_shutdown(__BFILE__, __LINE__, fail, log2str("Timeout waiting for PFCP ", exp_rx)); } } return pdu; } function f_ConnHdlr_pfcp_assoc_handler(charstring id) runs on ConnHdlr { var charstring key_name := g_pars.statsd_prefix & "gauge.pfcp.associated.value"; var StatsDMetricKeys statsd_keys := { valueof(ts_StatsDMetricKey(key_name, "g")) }; var StatsDMetrics statsd_snapshot := f_statsd_snapshot(statsd_keys, since_last_snapshot := false); var boolean pfcp_associated := statsd_snapshot[0].val == 1; if (not pfcp_associated) { log("Waiting for IUT to associate over PFCP"); f_ConnHdlr_pfcp_assoc_setup(); } setverdict(pass); } /* 256 is maxnoofE-RABs (see S1AP_Constants.asn) */ type record length(1..256) of ERab ERabList; type record ERab { E_RAB_ID erab_id, /* ::= INTEGER (0..15, ...) */ ERabParams u2c, /* UPF -> Core params */ ERabParams c2u, /* Core -> UPF params */ ERabParams a2u, /* Access -> UPF params */ ERabParams u2a, /* UPF -> Access params */ OCT8 pfcp_loc_seid, OCT8 pfcp_rem_seid optional }; type record ERabParams { GTP_TEID teid, /* ::= OCTET STRING (SIZE (4)) */ charstring tla /* Transport Layer Address */ }; private const S1AP_IEs.Cause c_REL_CMD_CAUSE := { nas := normal_release }; function f_ConnHdlr_tx_erab_setup_req(in ERabList erabs) runs on ConnHdlr { var template (value) E_RABToBeSetupListBearerSUReq items; var template (value) E_RABLevelQoSParameters qos_params; var ENB_UE_S1AP_ID enb_ue_id := g_pars.idx; qos_params := valueof(ts_E_RABLevelQoSParameters); for (var integer i := 0; i < lengthof(erabs); i := i + 1) { var template (value) E_RABToBeSetupItemBearerSUReq item; var template (value) TransportLayerAddress tla; var ERabParams epars := erabs[i].u2c; tla := oct2bit(f_inet_addr(epars.tla)); item := ts_S1AP_RABToBeSetupItemBearerSUReq(rab_id := erabs[i].erab_id, qos_params := qos_params, tla := tla, gtp_teid := epars.teid, nas_pdu := ''O); items[i] := ts_S1AP_RABToBeSetupListBearerSUReq(item)[0]; } f_ConnHdlr_tx_s1ap_from_mme(ts_S1AP_RABSetupReq(g_pars.mme_ue_id, enb_ue_id, items)); } function f_ConnHdlr_rx_erab_setup_req(in ERabList erabs) runs on ConnHdlr return S1AP_PDU { var template (present) E_RABToBeSetupListBearerSUReq items; var ENB_UE_S1AP_ID enb_ue_id := g_pars.idx; var S1AP_PDU pdu; for (var integer i := 0; i < lengthof(erabs); i := i + 1) { var template (present) E_RABToBeSetupItemBearerSUReq item; var template (present) TransportLayerAddress tla; var ERabParams epars := erabs[i].a2u; tla := oct2bit(f_inet_addr(epars.tla)); item := tr_S1AP_RABToBeSetupItemBearerSUReq(rab_id := erabs[i].erab_id, qos_params := ?, tla := tla, gtp_teid := epars.teid, nas_pdu := ''O); items[i] := tr_S1AP_RABToBeSetupListBearerSUReq(item)[0]; } f_ConnHdlr_rx_s1ap_from_mme(pdu, tr_S1AP_RABSetupReq(g_pars.mme_ue_id, enb_ue_id, items)); return pdu; } function f_ConnHdlr_tx_erab_setup_rsp(in ERabList erabs) runs on ConnHdlr { var template (value) E_RABSetupListBearerSURes items; var ENB_UE_S1AP_ID enb_ue_id := g_pars.idx; for (var integer i := 0; i < lengthof(erabs); i := i + 1) { var template (value) E_RABSetupItemBearerSURes item; var template (value) TransportLayerAddress tla; var ERabParams epars := erabs[i].u2a; tla := oct2bit(f_inet_addr(epars.tla)); item := ts_S1AP_RABSetupItemBearerSURes(rab_id := erabs[i].erab_id, tla := tla, gtp_teid := epars.teid); items[i] := ts_S1AP_RABSetupListBearerSURes(item)[0]; } f_ConnHdlr_tx_s1ap_from_enb(ts_S1AP_RABSetupRsp(g_pars.mme_ue_id, enb_ue_id, items)); } function f_ConnHdlr_rx_erab_setup_rsp(in ERabList erabs) runs on ConnHdlr return S1AP_PDU { var template (present) E_RABSetupListBearerSURes items; var ENB_UE_S1AP_ID enb_ue_id := g_pars.idx; var S1AP_PDU pdu; for (var integer i := 0; i < lengthof(erabs); i := i + 1) { var template (present) E_RABSetupItemBearerSURes item; var template (present) TransportLayerAddress tla; var ERabParams epars := erabs[i].c2u; tla := oct2bit(f_inet_addr(epars.tla)); item := tr_S1AP_RABSetupItemBearerSURes(rab_id := erabs[i].erab_id, tla := tla, gtp_teid := epars.teid); items[i] := tr_S1AP_RABSetupListBearerSURes(item)[0]; } f_ConnHdlr_rx_s1ap_from_enb(pdu, tr_S1AP_RABSetupRsp(g_pars.mme_ue_id, enb_ue_id, items)); return pdu; } private function f_ts_E_RABList(in ERabList erabs, S1AP_IEs.Cause cause) return template (value) E_RABList { var template (value) E_RABList items; for (var integer i := 0; i < lengthof(erabs); i := i + 1) { var template (value) E_RABItem item; item := ts_E_RABItem(erabs[i].erab_id, cause); items[i] := ts_E_RABList(item)[0]; } return items; } private function f_tr_E_RABList(in ERabList erabs, S1AP_IEs.Cause cause) return template (present) E_RABList { var template (present) E_RABList items; for (var integer i := 0; i < lengthof(erabs); i := i + 1) { var template (present) E_RABItem item; item := tr_E_RABItem(erabs[i].erab_id, cause); items[i] := tr_E_RABList(item)[0]; } return items; } function f_ConnHdlr_tx_erab_release_cmd(in ERabList erabs, S1AP_IEs.Cause cause := c_REL_CMD_CAUSE) runs on ConnHdlr { var template (value) E_RABList items := f_ts_E_RABList(erabs, cause); var ENB_UE_S1AP_ID enb_ue_id := g_pars.idx; f_ConnHdlr_tx_s1ap_from_mme(ts_S1AP_RABReleaseCmd(g_pars.mme_ue_id, enb_ue_id, items)); } function f_ConnHdlr_rx_erab_release_cmd(in ERabList erabs, S1AP_IEs.Cause cause := c_REL_CMD_CAUSE) runs on ConnHdlr return S1AP_PDU { var template (present) E_RABList items := f_tr_E_RABList(erabs, cause); var ENB_UE_S1AP_ID enb_ue_id := g_pars.idx; var S1AP_PDU pdu; f_ConnHdlr_rx_s1ap_from_mme(pdu, tr_S1AP_RABReleaseCmd(g_pars.mme_ue_id, enb_ue_id, items)); return pdu; } function f_ConnHdlr_tx_erab_release_rsp(in ERabList erabs) runs on ConnHdlr { var template (value) E_RABReleaseListBearerRelComp items; var ENB_UE_S1AP_ID enb_ue_id := g_pars.idx; for (var integer i := 0; i < lengthof(erabs); i := i + 1) { var template (value) E_RABReleaseItemBearerRelComp item; item := ts_E_RABReleaseItemBearerRelComp(erabs[i].erab_id); items[i] := ts_E_RABReleaseListBearerRelComp(item)[0]; } f_ConnHdlr_tx_s1ap_from_enb(ts_S1AP_RABReleaseRsp(g_pars.mme_ue_id, enb_ue_id, items)); } function f_ConnHdlr_rx_erab_release_rsp(in ERabList erabs) runs on ConnHdlr return S1AP_PDU { var template (present) E_RABReleaseListBearerRelComp items; var ENB_UE_S1AP_ID enb_ue_id := g_pars.idx; var S1AP_PDU pdu; for (var integer i := 0; i < lengthof(erabs); i := i + 1) { var template (present) E_RABReleaseItemBearerRelComp item; item := tr_E_RABReleaseItemBearerRelComp(erabs[i].erab_id); items[i] := tr_E_RABReleaseListBearerRelComp(item)[0]; } f_ConnHdlr_rx_s1ap_from_enb(pdu, tr_S1AP_RABReleaseRsp(g_pars.mme_ue_id, enb_ue_id, items)); return pdu; } function f_ConnHdlr_tx_erab_release_ind(in ERabList erabs, S1AP_IEs.Cause cause := c_REL_CMD_CAUSE) runs on ConnHdlr { var template (value) E_RABList items := f_ts_E_RABList(erabs, cause); var ENB_UE_S1AP_ID enb_ue_id := g_pars.idx; f_ConnHdlr_tx_s1ap_from_enb(ts_S1AP_RABReleaseInd(g_pars.mme_ue_id, enb_ue_id, items)); } function f_ConnHdlr_rx_erab_release_ind(in ERabList erabs, S1AP_IEs.Cause cause := c_REL_CMD_CAUSE) runs on ConnHdlr return S1AP_PDU { var template (present) E_RABList items := f_tr_E_RABList(erabs, cause); var ENB_UE_S1AP_ID enb_ue_id := g_pars.idx; var S1AP_PDU pdu; f_ConnHdlr_rx_s1ap_from_enb(pdu, tr_S1AP_RABReleaseInd(g_pars.mme_ue_id, enb_ue_id, items)); return pdu; } function f_ConnHdlr_tx_initial_ctx_setup_req(in ERabList erabs) runs on ConnHdlr { var template (value) E_RABToBeSetupListCtxtSUReq items; var template (value) E_RABLevelQoSParameters qos_params; var ENB_UE_S1AP_ID enb_ue_id := g_pars.idx; qos_params := valueof(ts_E_RABLevelQoSParameters); for (var integer i := 0; i < lengthof(erabs); i := i + 1) { var template (value) E_RABToBeSetupItemCtxtSUReq item; var template (value) TransportLayerAddress tla; var ERabParams epars := erabs[i].u2c; tla := oct2bit(f_inet_addr(epars.tla)); item := ts_E_RABToBeSetupItemCtxtSUReq(rab_id := erabs[i].erab_id, qos_params := qos_params, tla := tla, gtp_teid := epars.teid, nas_pdu := omit); items[i] := ts_E_RABToBeSetupListCtxtSUReq(item)[0]; } f_ConnHdlr_tx_s1ap_from_mme(ts_S1AP_IntialCtxSetupReq(mme_id := g_pars.mme_ue_id, enb_id := enb_ue_id, max_br := ts_UEAggregateMaximumBitrate, rab_setup_items := items, ue_sec_par := ts_UESecurityCapabilities, sec_key := f_rnd_bitstring(256))); } function f_ConnHdlr_rx_initial_ctx_setup_req(in ERabList erabs) runs on ConnHdlr return S1AP_PDU { var template (present) E_RABToBeSetupListCtxtSUReq items; var ENB_UE_S1AP_ID enb_ue_id := g_pars.idx; var S1AP_PDU pdu; for (var integer i := 0; i < lengthof(erabs); i := i + 1) { var template (present) E_RABToBeSetupItemCtxtSUReq item; var template (present) TransportLayerAddress tla; var ERabParams epars := erabs[i].a2u; tla := oct2bit(f_inet_addr(epars.tla)); item := tr_E_RABToBeSetupItemCtxtSUReq(rab_id := erabs[i].erab_id, qos_params := ?, tla := tla, gtp_teid := epars.teid, nas_pdu := omit); items[i] := tr_E_RABToBeSetupListCtxtSUReq(item)[0]; } f_ConnHdlr_rx_s1ap_from_mme(pdu, tr_S1AP_IntialCtxSetupReq(mme_id := g_pars.mme_ue_id, enb_id := enb_ue_id, max_br := tr_UEAggregateMaximumBitrate, rab_setup_items := items, ue_sec_par := tr_UESecurityCapabilities)); return pdu; } function f_ConnHdlr_tx_initial_ctx_setup_rsp(in ERabList erabs) runs on ConnHdlr { var template (value) E_RABSetupListCtxtSURes items; var ENB_UE_S1AP_ID enb_ue_id := g_pars.idx; for (var integer i := 0; i < lengthof(erabs); i := i + 1) { var template (value) E_RABSetupItemCtxtSURes item; var template (value) TransportLayerAddress tla; var ERabParams epars := erabs[i].u2a; tla := oct2bit(f_inet_addr(epars.tla)); item := ts_S1AP_RABSetupItemCtxtSURes(rab_id := erabs[i].erab_id, tla := tla, gtp_teid := epars.teid); items[i] := ts_S1AP_RABSetupListCtxtSURes(item)[0]; } f_ConnHdlr_tx_s1ap_from_enb(ts_S1AP_InitialCtxSetupResp(g_pars.mme_ue_id, enb_ue_id, items)); } function f_ConnHdlr_rx_initial_ctx_setup_rsp(in ERabList erabs) runs on ConnHdlr return S1AP_PDU { var template (present) E_RABSetupListCtxtSURes items; var ENB_UE_S1AP_ID enb_ue_id := g_pars.idx; var S1AP_PDU pdu; for (var integer i := 0; i < lengthof(erabs); i := i + 1) { var template (present) E_RABSetupItemCtxtSURes item; var template (present) TransportLayerAddress tla; var ERabParams epars := erabs[i].c2u; tla := oct2bit(f_inet_addr(epars.tla)); item := tr_S1AP_RABSetupItemCtxtSURes(rab_id := erabs[i].erab_id, tla := tla, gtp_teid := epars.teid); items[i] := tr_S1AP_RABSetupListCtxtSURes(item)[0]; } f_ConnHdlr_rx_s1ap_from_enb(pdu, tr_S1AP_InitialCtxSetupResp(g_pars.mme_ue_id, enb_ue_id, items)); return pdu; } function f_ConnHdlr_rx_session_establish_req(in ERab erab) runs on ConnHdlr return PDU_PFCP { var OCT4 addr := f_inet_addr(g_pars.pfcp_rem_addr); /* Packet Detection Rules */ var template (present) Outer_Header_Removal ohr; var template (present) Create_PDR pdr1; var template (present) Create_PDR pdr2; ohr := tr_PFCP_Outer_Header_Removal(GTP_U_UDP_IPV4); pdr1 := tr_PFCP_Create_PDR(pdr_id := 1, /* -- for Core -> Access */ pdi := tr_PFCP_PDI(CORE), ohr := ohr, far_id := 1); pdr2 := tr_PFCP_Create_PDR(pdr_id := 2, /* -- for Access -> Core */ pdi := tr_PFCP_PDI(ACCESS), ohr := ohr, far_id := 2); /* Forwarding Action Rules */ var template (present) Outer_Header_Creation ohc2; var template (present) Forwarding_Parameters fpars2; var template (present) Create_FAR far1; var template (present) Create_FAR far2; ohc2 := tr_PFCP_Outer_Header_Creation_GTP_ipv4(erab.u2c.teid, f_inet_addr(erab.u2c.tla)); fpars2 := tr_PFCP_Forwarding_Parameters(CORE, ohc2); far1 := tr_PFCP_Create_FAR(far_id := 1, /* -- for Core -> Access */ aa := tr_PFCP_Apply_Action_DROP, fp := omit); far2 := tr_PFCP_Create_FAR(far_id := 2, /* -- for Access -> Core */ aa := tr_PFCP_Apply_Action_FORW, fp := fpars2); /* The final Session Establishment Request PDU */ var template (present) PDU_PFCP tr_pdu; tr_pdu := tr_PFCP_Session_Est_Req(node_id := tr_PFCP_Node_ID_ipv4(addr), cp_f_seid := tr_PFCP_F_SEID_ipv4(addr), create_pdr := {pdr1, pdr2}, create_far := {far1, far2}); return f_ConnHdlr_pfcp_expect(tr_pdu); } function f_ConnHdlr_tx_session_establish_resp(in ERab erab, in PDU_PFCP req) runs on ConnHdlr { var OCT4 addr := f_inet_addr(g_pars.pfcp_loc_addr); var PFCP_Session_Establishment_Request serq; serq := req.message_body.pfcp_session_establishment_request; /* Created Packet Detection Rules */ var template (value) Created_PDR pdr1; var template (value) Created_PDR pdr2; pdr1 := ts_PFCP_Created_PDR(pdr_id := serq.create_PDR_list[0].grouped_ie.pdr_id, local_F_TEID := ts_PFCP_F_TEID_ipv4(erab.c2u.teid, f_inet_addr(erab.c2u.tla))); pdr2 := ts_PFCP_Created_PDR(pdr_id := serq.create_PDR_list[1].grouped_ie.pdr_id, local_F_TEID := ts_PFCP_F_TEID_ipv4(erab.a2u.teid, f_inet_addr(erab.a2u.tla))); /* The final Session Establishment Response PDU */ var template (value) PDU_PFCP resp; resp := ts_PFCP_Session_Est_Resp(seq_nr := req.sequence_number, node_id := ts_PFCP_Node_ID_ipv4(addr), seid := erab.pfcp_rem_seid, f_seid := ts_PFCP_F_SEID_ipv4(addr, erab.pfcp_loc_seid), created_pdr := {pdr1, pdr2}); PFCP.send(resp); } function f_ConnHdlr_rx_session_modify_req(in ERab erab) runs on ConnHdlr return PDU_PFCP { /* Forwarding Action Rules */ var template (present) Outer_Header_Creation ohc1; var template (present) Update_Forwarding_Parameters fpars1; var template (present) Update_FAR far1; ohc1 := tr_PFCP_Outer_Header_Creation_GTP_ipv4(erab.u2a.teid, f_inet_addr(erab.u2a.tla)); fpars1 := tr_PFCP_Update_Forwarding_Parameters(ohc := ohc1); far1 := tr_PFCP_Update_FAR(far_id := 1, /* -- for Core -> Access */ aa := tr_PFCP_Apply_Action_FORW, fp := fpars1); /* The final Session Modification Request PDU */ var template (present) PDU_PFCP tr_pdu; tr_pdu := tr_PFCP_Session_Mod_Req(seid := erab.pfcp_loc_seid, update_far := far1); return f_ConnHdlr_pfcp_expect(tr_pdu); } function f_ConnHdlr_tx_session_modify_resp(in ERab erab, in PDU_PFCP req) runs on ConnHdlr { /* The final Session Modification Response PDU */ var template (value) PDU_PFCP resp; resp := ts_PFCP_Session_Mod_Resp(seq_nr := req.sequence_number, seid := erab.pfcp_rem_seid); PFCP.send(resp); } function f_ConnHdlr_rx_session_delete_req(in ERab erab) runs on ConnHdlr return PDU_PFCP { /* The final Session Deletion Request PDU */ var template (present) PDU_PFCP tr_pdu; tr_pdu := tr_PFCP_Session_Del_Req(erab.pfcp_loc_seid); return f_ConnHdlr_pfcp_expect(tr_pdu); } function f_ConnHdlr_tx_session_delete_resp(in ERab erab, in PDU_PFCP req) runs on ConnHdlr { /* The final Session Deletion Response PDU */ var template (value) PDU_PFCP resp; resp := ts_PFCP_Session_Del_Resp(seq_nr := req.sequence_number, seid := erab.pfcp_rem_seid); PFCP.send(resp); } function f_ConnHdlr_session_establish(inout ERabList erabs) runs on ConnHdlr { for (var integer i := 0; i < lengthof(erabs); i := i + 1) { log("UPF <- S1GW: PFCP Session Establishment Request for E-RAB ID ", erabs[i].erab_id); var PDU_PFCP pdu := f_ConnHdlr_rx_session_establish_req(erabs[i]); /* store peer's SEID, so that it can be used in outgoing PDUs later */ erabs[i].pfcp_rem_seid := pdu.message_body.pfcp_session_establishment_request.CP_F_SEID.seid; /* ask PFCPEM to route PDUs with the local SEID to us */ f_PFCPEM_subscribe_seid(erabs[i].pfcp_loc_seid); log("UPF -> S1GW: PFCP Session Establishment Response for E-RAB ID ", erabs[i].erab_id); f_ConnHdlr_tx_session_establish_resp(erabs[i], pdu); } } function f_ConnHdlr_session_modify(in ERabList erabs) runs on ConnHdlr { for (var integer i := 0; i < lengthof(erabs); i := i + 1) { log("UPF <- S1GW: PFCP Session Modification Request for E-RAB ID ", erabs[i].erab_id); var PDU_PFCP pdu := f_ConnHdlr_rx_session_modify_req(erabs[i]); log("UPF -> S1GW: PFCP Session Modification Response for E-RAB ID ", erabs[i].erab_id); f_ConnHdlr_tx_session_modify_resp(erabs[i], pdu); } } function f_ConnHdlr_session_delete(in ERabList erabs) runs on ConnHdlr { for (var integer i := 0; i < lengthof(erabs); i := i + 1) { log("UPF <- S1GW: PFCP Session Deletion Request for E-RAB ID ", erabs[i].erab_id); var PDU_PFCP pdu := f_ConnHdlr_rx_session_delete_req(erabs[i]); log("UPF -> S1GW: PFCP Session Deletion Response for E-RAB ID ", erabs[i].erab_id); f_ConnHdlr_tx_session_delete_resp(erabs[i], pdu); /* ask PFCPEM to *not* route PDUs with this SEID to us */ f_PFCPEM_unsubscribe_seid(erabs[i].pfcp_loc_seid); } } function f_ConnHdlr_erab_setup_req(inout ERabList erabs) runs on ConnHdlr { const OCT8 c_SEID0 := '0000000000000000'O; /* This code block cannot be executed by more than one component at a time because * the S1AP E-RAB SETUP REQUEST triggers the IUT to send PFCP Session Establishment * Request PDU(s), with need to be routed to the respective ConnHdlr component (us). * As per 3GPP TS 29.244, section 7.2.2.4.2, these PFCP PDUs shall all have SEID=0, * so that the PFCPEM component cannot route them unambiguously. This is why we * need to ensure that only one ConnHdlr is triggering PFCP session establishment * at the given moment of time. */ f_Mutex_lock(__BFILE__, __LINE__); f_PFCPEM_subscribe_seid(c_SEID0); log("eNB <- [S1GW <- MME]: E-RAB SETUP REQUEST"); f_ConnHdlr_tx_erab_setup_req(erabs); f_ConnHdlr_session_establish(erabs); /* We're done establishing PFCP sessions, so at this point we no longer expect to * receive Session Establishment Request PDUs with SEID=0. Unregister and unlock * the mutex, enabling other components to establish PFCP sessions after us. */ f_PFCPEM_unsubscribe_seid(c_SEID0); f_Mutex_unlock(__BFILE__, __LINE__); log("[eNB <- S1GW] <- MME: E-RAB SETUP REQUEST"); f_ConnHdlr_rx_erab_setup_req(erabs); } function f_ConnHdlr_erab_setup_rsp(in ERabList erabs) runs on ConnHdlr { log("[eNB -> S1GW] -> MME: E-RAB SETUP RESPONSE"); f_ConnHdlr_tx_erab_setup_rsp(erabs); f_ConnHdlr_session_modify(erabs); log("eNB -> [S1GW -> MME]: E-RAB SETUP RESPONSE"); f_ConnHdlr_rx_erab_setup_rsp(erabs); } function f_ConnHdlr_erab_release_cmd(inout ERabList erabs, S1AP_IEs.Cause cause := c_REL_CMD_CAUSE) runs on ConnHdlr { log("eNB <- [S1GW <- MME]: E-RAB RELEASE COMMAND"); f_ConnHdlr_tx_erab_release_cmd(erabs, cause); f_ConnHdlr_session_delete(erabs); log("[eNB <- S1GW] <- MME: E-RAB RELEASE COMMAND"); f_ConnHdlr_rx_erab_release_cmd(erabs, cause); } function f_ConnHdlr_erab_release_rsp(inout ERabList erabs) runs on ConnHdlr { log("[eNB -> S1GW] -> MME: E-RAB RELEASE RESPONSE"); f_ConnHdlr_tx_erab_release_rsp(erabs); log("eNB -> [S1GW -> MME]: E-RAB RELEASE RESPONSE"); f_ConnHdlr_rx_erab_release_rsp(erabs); } function f_ConnHdlr_erab_release_ind(inout ERabList erabs, S1AP_IEs.Cause cause := c_REL_CMD_CAUSE) runs on ConnHdlr { log("[eNB -> S1GW] -> MME: E-RAB RELEASE INDICATION"); f_ConnHdlr_tx_erab_release_ind(erabs, cause); f_ConnHdlr_session_delete(erabs); log("eNB -> [S1GW -> MME]: E-RAB RELEASE INDICATION"); f_ConnHdlr_rx_erab_release_ind(erabs, cause); } function f_ConnHdlr_initial_ctx_setup_req(inout ERabList erabs) runs on ConnHdlr { const OCT8 c_SEID0 := '0000000000000000'O; /* This code block cannot be executed by more than one component at a time because * the S1AP E-RAB SETUP REQUEST triggers the IUT to send PFCP Session Establishment * Request PDU(s), with need to be routed to the respective ConnHdlr component (us). * As per 3GPP TS 29.244, section 7.2.2.4.2, these PFCP PDUs shall all have SEID=0, * so that the PFCPEM component cannot route them unambiguously. This is why we * need to ensure that only one ConnHdlr is triggering PFCP session establishment * at the given moment of time. */ f_Mutex_lock(__BFILE__, __LINE__); f_PFCPEM_subscribe_seid(c_SEID0); log("eNB <- [S1GW <- MME]: INITIAL CONTEXT SETUP REQUEST"); f_ConnHdlr_tx_initial_ctx_setup_req(erabs); f_ConnHdlr_session_establish(erabs); /* We're done establishing PFCP sessions, so at this point we no longer expect to * receive Session Establishment Request PDUs with SEID=0. Unregister and unlock * the mutex, enabling other components to establish PFCP sessions after us. */ f_PFCPEM_unsubscribe_seid(c_SEID0); f_Mutex_unlock(__BFILE__, __LINE__); log("[eNB <- S1GW] <- MME: INITIAL CONTEXT SETUP REQUEST"); f_ConnHdlr_rx_initial_ctx_setup_req(erabs); } function f_ConnHdlr_initial_ctx_setup_rsp(inout ERabList erabs) runs on ConnHdlr { log("[eNB -> S1GW] -> MME: INITIAL CONTEXT SETUP RESPONSE"); f_ConnHdlr_tx_initial_ctx_setup_rsp(erabs); f_ConnHdlr_session_modify(erabs); log("eNB -> [S1GW -> MME]: INITIAL CONTEXT SETUP RESPONSE"); f_ConnHdlr_rx_initial_ctx_setup_rsp(erabs); } }