laforge submitted this change.

View Change

Approvals: pespin: Looks good to me, approved laforge: Looks good to me, but someone else must approve Jenkins Builder: Verified
library/PFCP_Emulation: a better PDU routing concept

In recently merged 2962d170 I wrongly assumed, that SEID of outgoing
PFCP PDUs can be used to correlate and route the incoming PDUs. In
fact, the PFCP peers use two different SEID values, negotiating them
using the F-SEID IE.

We could have implemented a logic to look for F-SEID in the outgoing
PDUs, store and then use it for routing. However, a more flexible
approach is to allow the the PFCP_ConnHdlr components to subscribe
and unsubscribe to/from specific SEID values explicitly.

In this spirit, let's allow the PFCP_ConnHdlr components to subscribe
and unsubscribe to/from broadcast PDUs (i.e. those, for which the
PFCPEM component could not find a single recipient) explicitly.

Implicit routing using the SeqNr remains unchanged and will be
performed by the PFCPEM component automatically like before.

Change-Id: I25802471519fa297ad4cb2b056adaa6748b00af2
Related: 2962d170 "library/PFCP_Emulation: fix routing of incoming PDUs"
---
M hnbgw/HNBGW_Tests.ttcn
M library/PFCP_Emulation.ttcn
M s1gw/S1GW_ConnHdlr.ttcn
3 files changed, 215 insertions(+), 38 deletions(-)

diff --git a/hnbgw/HNBGW_Tests.ttcn b/hnbgw/HNBGW_Tests.ttcn
index 86a4f3f..203b11e 100644
--- a/hnbgw/HNBGW_Tests.ttcn
+++ b/hnbgw/HNBGW_Tests.ttcn
@@ -1942,12 +1942,15 @@
}

friend function f_tc_ps_rab_assignment_with_pfcp(charstring id, TestHdlrParams pars) runs on ConnHdlr {
+ const OCT8 c_SEID0 := '0000000000000000'O;
+ const OCT8 c_SEID1 := '1111111111111111'O;
var RANAP_PDU tx;
var RANAP_PDU rx;

f_init_handler(pars);

- f_pfcp_register();
+ /* ask PFCPEM to route all PDUs to us */
+ f_PFCPEM_subscribe_bcast();

var PDU_PFCP m;
var Node_ID upf_node_id := valueof(ts_PFCP_Node_ID_fqdn("\07osmocom\03org"));
@@ -1956,6 +1959,13 @@
PFCP.send(ts_PFCP_Assoc_Setup_Resp(m.sequence_number, upf_node_id,
ts_PFCP_Cause(REQUEST_ACCEPTED), 1234));

+ /* Subscribe for PFCP Session Establishment Request PDU(s), which are
+ * expected to have SEID set to 0, as per 3GPP TS 29.244, section 7.2.2.4.2. */
+ f_PFCPEM_subscribe_seid(c_SEID0);
+
+ /* ask PFCPEM to *not* route all PDUs to us anymore */
+ f_PFCPEM_unsubscribe_bcast();
+
tx := f_build_initial_ue(g_pars);
f_iuh2iu_connect(tx);

@@ -1969,12 +1979,17 @@

/* Expect PFCP Session Establishment Request. */
m := f_pfcp_expect(tr_PFCP_Session_Est_Req());
+ /* Ask PFCPEM to route PDUs with to be indicated F-SEID to us. */
+ f_PFCPEM_subscribe_seid(c_SEID1);
+ /* We no longer expect to receive PFCP Session Establishment Request PDU(s). */
+ f_PFCPEM_unsubscribe_seid(c_SEID0);
+
var PFCP_Session_Establishment_Request serq := m.message_body.pfcp_session_establishment_request;
var F_SEID hnbgw_f_seid := serq.CP_F_SEID;

/* Acting as UPF, invent a new PFCP SEID to send to HNBGW. Respond to the Session Establishment.
* The PFCP response must have the same sequence_number as the request. */
- var template F_SEID up_f_seid := ts_PFCP_F_SEID_ipv4(f_inet_addr("127.0.0.1"), '1111111111111111'O);
+ var template F_SEID up_f_seid := ts_PFCP_F_SEID_ipv4(f_inet_addr("127.0.0.1"), c_SEID1);
var template F_TEID f_teid1 := ts_PFCP_F_TEID_ipv4(gtp_pars.core.local.teid,
f_inet_addr(gtp_pars.core.local.addr));
var template F_TEID f_teid2 := ts_PFCP_F_TEID_ipv4(gtp_pars.access.local.teid,
@@ -2020,6 +2035,9 @@
m := f_pfcp_expect(tr_PFCP_Session_Del_Req(up_f_seid.seid));
PFCP.send(ts_PFCP_Session_Del_Resp(m.sequence_number, hnbgw_f_seid.seid));

+ /* ask PFCPEM to *not* route PDUs with this specific SEID to us */
+ f_PFCPEM_unsubscribe_seid(c_SEID1);
+
f_sleep(2.0);
}

@@ -2050,7 +2068,8 @@

f_init_handler(pars);

- f_pfcp_register();
+ /* ask PFCPEM to route all PDUs to us */
+ f_PFCPEM_subscribe_bcast();
activate(as_disallow_pfcp());

tx := f_build_initial_ue(g_pars);
diff --git a/library/PFCP_Emulation.ttcn b/library/PFCP_Emulation.ttcn
index c917157..edb1f9c 100644
--- a/library/PFCP_Emulation.ttcn
+++ b/library/PFCP_Emulation.ttcn
@@ -1,6 +1,6 @@
/* PFCP Emulation in TTCN-3
*
- * (C) 2022 sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
+ * (C) 2022-2024 sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
* All rights reserved.
*
* Released under the terms of GNU General Public License, Version 2 or
@@ -14,6 +14,8 @@
import from IPL4asp_Types all;
import from General_Types all;
import from Osmocom_Types all;
+import from Misc_Helpers all;
+
import from PFCP_Types all;
import from PFCP_Templates all;
import from PFCP_CodecPort all;
@@ -53,7 +55,12 @@
var integer g_pfcp_conn_id;
var integer g_recovery_timestamp;

- var PFCPEM_conns g_conns;
+ /* List of PFCP_ConnHdlr subscribed for broadcast */
+ var PFCPEM_conns g_conns_bcast;
+ /* List of PFCP_ConnHdlr subscribed for SEID */
+ var PFCPEM_conns g_conns_seqnr;
+ /* List of PFCP_ConnHdlr subscribed for SeqNr */
+ var PFCPEM_conns g_conns_seid;

var integer g_next_sequence_nr_state;
};
@@ -69,16 +76,16 @@
type record PFCPEM_conn {
PFCP_ConnHdlr vc_conn,
OCT8 seid optional,
- LIN3_BO_LAST pfcp_msg_sequence_number optional
+ LIN3_BO_LAST seqnr optional
};

type record of PFCPEM_conn PFCPEM_conns;

private function f_PFCPEM_conn_by_seqnr(LIN3_BO_LAST seqnr)
runs on PFCP_Emulation_CT return PFCP_ConnHdlr {
- for (var integer i := 0; i < lengthof(g_conns); i := i + 1) {
- if (seqnr == g_conns[i].pfcp_msg_sequence_number) {
- return g_conns[i].vc_conn;
+ for (var integer i := 0; i < lengthof(g_conns_seqnr); i := i + 1) {
+ if (seqnr == g_conns_seqnr[i].seqnr) {
+ return g_conns_seqnr[i].vc_conn;
}
}
return null;
@@ -86,9 +93,9 @@

private function f_PFCPEM_conn_by_seid(OCT8 seid)
runs on PFCP_Emulation_CT return PFCP_ConnHdlr {
- for (var integer i := 0; i < lengthof(g_conns); i := i + 1) {
- if (seid == g_conns[i].seid) {
- return g_conns[i].vc_conn;
+ for (var integer i := 0; i < lengthof(g_conns_seid); i := i + 1) {
+ if (seid == g_conns_seid[i].seid) {
+ return g_conns_seid[i].vc_conn;
}
}
return null;
@@ -100,6 +107,7 @@

vc_conn := f_PFCPEM_conn_by_seqnr(pdu.sequence_number);
if (vc_conn != null) {
+ f_PFCPEM_conns_seqnr_del(pdu.sequence_number);
return vc_conn;
}

@@ -111,16 +119,97 @@
return vc_conn;
};

-private function f_PFCPEM_conn_add_or_update(in PFCPEM_conn conn)
-runs on PFCP_Emulation_CT {
- for (var integer i := 0; i < lengthof(g_conns); i := i + 1) {
- if (g_conns[i].vc_conn == conn.vc_conn) {
- g_conns[i] := conn; /* update */
- return;
+/* subscribe/unsubscribe for/from broadcast PDUs */
+private function f_PFCPEM_conns_bcast_add(PFCP_ConnHdlr vc_conn)
+runs on PFCP_Emulation_CT return boolean {
+ for (var integer i := 0; i < lengthof(g_conns_bcast); i := i + 1) {
+ if (g_conns_bcast[i].vc_conn == vc_conn) {
+ log(__SCOPE__, "(): vc_conn ", vc_conn, " is already in the list");
+ return false;
}
}
- /* Not in the list yet, add. */
- g_conns := g_conns & { conn };
+ g_conns_bcast := g_conns_bcast & {{vc_conn, omit, omit}};
+ log(__SCOPE__, "(): vc_conn ", vc_conn, " subscribed for broadcast");
+ return true;
+}
+private function f_PFCPEM_conns_bcast_del(PFCP_ConnHdlr vc_conn)
+runs on PFCP_Emulation_CT return boolean {
+ var PFCPEM_conns conns := { };
+
+ for (var integer i := 0; i < lengthof(g_conns_bcast); i := i + 1) {
+ if (g_conns_bcast[i].vc_conn != vc_conn) {
+ conns := conns & { g_conns_bcast[i] };
+ }
+ }
+ if (lengthof(conns) == lengthof(g_conns_bcast)) {
+ log(__SCOPE__, "(): vc_conn ", vc_conn, " was not in the list");
+ return false;
+ }
+ log(__SCOPE__, "(): vc_conn ", vc_conn, " unsubscribed from broadcast");
+ g_conns_bcast := conns;
+ return true;
+}
+
+/* subscribe/unsubscribe for/from PDUs with the given SeqNr value */
+private function f_PFCPEM_conns_seqnr_add(PFCP_ConnHdlr vc_conn,
+ LIN3_BO_LAST seqnr)
+runs on PFCP_Emulation_CT return boolean {
+ for (var integer i := 0; i < lengthof(g_conns_seqnr); i := i + 1) {
+ if (g_conns_seqnr[i].seqnr == seqnr) {
+ log(__SCOPE__, "(): SeqNr ", seqnr, " is already claimed",
+ " by vc_conn ", g_conns_seqnr[i].vc_conn);
+ return false;
+ }
+ }
+ g_conns_seqnr := g_conns_seqnr & { {vc_conn, omit, seqnr} };
+ return true;
+}
+private function f_PFCPEM_conns_seqnr_del(LIN3_BO_LAST seqnr)
+runs on PFCP_Emulation_CT return boolean {
+ var PFCPEM_conns conns := { };
+
+ for (var integer i := 0; i < lengthof(g_conns_seqnr); i := i + 1) {
+ if (g_conns_seqnr[i].seqnr != seqnr) {
+ conns := conns & { g_conns_seqnr[i] };
+ }
+ }
+ if (lengthof(conns) == lengthof(g_conns_seqnr)) {
+ log(__SCOPE__, "(): SeqNr ", seqnr, " was not in the list");
+ return false;
+ }
+ g_conns_seqnr := conns;
+ return true;
+}
+
+/* subscribe/unsubscribe for/from PDUs with the given SEID value */
+private function f_PFCPEM_conns_seid_add(PFCP_ConnHdlr vc_conn,
+ OCT8 seid)
+runs on PFCP_Emulation_CT return boolean {
+ for (var integer i := 0; i < lengthof(g_conns_seid); i := i + 1) {
+ if (g_conns_seid[i].seid == seid) {
+ log(__SCOPE__, "(): SEID ", seid, " is already claimed",
+ " by vc_conn ", g_conns_seid[i].vc_conn);
+ return false;
+ }
+ }
+ g_conns_seid := g_conns_seid & { {vc_conn, seid, omit} };
+ return true;
+}
+private function f_PFCPEM_conns_seid_del(OCT8 seid)
+runs on PFCP_Emulation_CT return boolean {
+ var PFCPEM_conns conns := { };
+
+ for (var integer i := 0; i < lengthof(g_conns_seid); i := i + 1) {
+ if (g_conns_seid[i].seid != seid) {
+ conns := conns & { g_conns_seid[i] };
+ }
+ }
+ if (lengthof(conns) == lengthof(g_conns_seid)) {
+ log(__SCOPE__, "(): SEID ", seid, " was not in the list");
+ return false;
+ }
+ g_conns_seid := conns;
+ return true;
}

private function f_init(PFCP_Emulation_Cfg cfg) runs on PFCP_Emulation_CT {
@@ -133,7 +222,9 @@
g_recovery_timestamp := f_rnd_int(4294967296);
g_pfcp_cfg := cfg;

- g_conns := {};
+ g_conns_bcast := { };
+ g_conns_seqnr := { };
+ g_conns_seid := { };

g_next_sequence_nr_state := (1 + f_rnd_int(1000)) * 10000;
}
@@ -142,6 +233,7 @@
var PFCP_ConnHdlr vc_conn;
var PFCP_Unitdata ud;
var PDU_PFCP pdu;
+ var OCT8 seid;

f_init(cfg);

@@ -162,9 +254,9 @@
log("found destination ", vc_conn);
CLIENT.send(ud.pdu) to vc_conn;
} else {
- log("sending to all conns: ", g_conns);
- for (var integer i := 0; i < lengthof(g_conns); i := i + 1) {
- CLIENT.send(ud.pdu) to g_conns[i].vc_conn;
+ for (var integer i := 0; i < lengthof(g_conns_bcast); i := i + 1) {
+ log("broadcasting to ", g_conns_bcast[i].vc_conn);
+ CLIENT.send(ud.pdu) to g_conns_bcast[i].vc_conn;
}
}
}
@@ -183,19 +275,33 @@
pdu := pdu
};

- f_PFCPEM_conn_add_or_update({vc_conn, pdu.seid, pdu.sequence_number});
+ /* route the response back to this vc_conn */
+ f_PFCPEM_conns_seqnr_add(vc_conn, pdu.sequence_number);

PFCP.send(ud);
}

- [] CLIENT_PROC.getcall(PFCPEM_register:{}) -> sender vc_conn {
- log("PFCP_Emulation main() CLIENT_PROC.getcall(PFCPEM_register)");
- f_PFCPEM_conn_add_or_update({vc_conn, omit, omit});
- CLIENT_PROC.reply(PFCPEM_register:{}) to vc_conn;
+ /* subscribe/unsubscribe for/from broadcast PDUs */
+ [] CLIENT_PROC.getcall(PFCPEM_subscribe_bcast:{?}) -> sender vc_conn {
+ var boolean res := f_PFCPEM_conns_bcast_add(vc_conn);
+ CLIENT_PROC.reply(PFCPEM_subscribe_bcast:{res}) to vc_conn;
+ }
+ [] CLIENT_PROC.getcall(PFCPEM_unsubscribe_bcast:{?}) -> sender vc_conn {
+ var boolean res := f_PFCPEM_conns_bcast_del(vc_conn);
+ CLIENT_PROC.reply(PFCPEM_unsubscribe_bcast:{res}) to vc_conn;
+ }
+
+ /* subscribe/unsubscribe for/from PDUs with the given SEID value */
+ [] CLIENT_PROC.getcall(PFCPEM_subscribe_seid:{?, ?}) -> param(seid := seid) sender vc_conn {
+ var boolean res := f_PFCPEM_conns_seid_add(vc_conn, seid);
+ CLIENT_PROC.reply(PFCPEM_subscribe_seid:{seid, res}) to vc_conn;
+ }
+ [] CLIENT_PROC.getcall(PFCPEM_unsubscribe_seid:{?, ?}) -> param(seid := seid) sender vc_conn {
+ var boolean res := f_PFCPEM_conns_seid_del(seid);
+ CLIENT_PROC.reply(PFCPEM_unsubscribe_seid:{seid, res}) to vc_conn;
}

[] CLIENT_PROC.getcall(PFCPEM_get_recovery_timestamp:{?}) -> sender vc_conn {
- log("PFCP_Emulation main() CLIENT_PROC.getcall(PFCPEM_get_recovery_timestamp)");
CLIENT_PROC.reply(PFCPEM_get_recovery_timestamp:{g_recovery_timestamp}) to vc_conn;
}
}
@@ -210,11 +316,17 @@
inout PDU_PFCP;
} with { extension "internal" };

-signature PFCPEM_register();
+signature PFCPEM_subscribe_bcast(out boolean success);
+signature PFCPEM_unsubscribe_bcast(out boolean success);
+signature PFCPEM_subscribe_seid(in OCT8 seid, out boolean success);
+signature PFCPEM_unsubscribe_seid(in OCT8 seid, out boolean success);
signature PFCPEM_get_recovery_timestamp(out integer rts);

type port PFCPEM_PROC_PT procedure {
- inout PFCPEM_register;
+ inout PFCPEM_subscribe_bcast;
+ inout PFCPEM_unsubscribe_bcast;
+ inout PFCPEM_subscribe_seid;
+ inout PFCPEM_unsubscribe_seid;
inout PFCPEM_get_recovery_timestamp;
} with { extension "internal" };

@@ -227,13 +339,47 @@
port PFCPEM_PROC_PT PFCP_PROC;
};

-function f_pfcp_register() runs on PFCP_ConnHdlr {
- PFCP_PROC.call(PFCPEM_register:{}) {
- [] PFCP_PROC.getreply(PFCPEM_register:{});
+/* subscribe/unsubscribe for/from broadcast PDUs */
+function f_PFCPEM_subscribe_bcast() runs on PFCP_ConnHdlr {
+ PFCP_PROC.call(PFCPEM_subscribe_bcast:{-}) {
+ [] PFCP_PROC.getreply(PFCPEM_subscribe_bcast:{true});
+ [] PFCP_PROC.getreply(PFCPEM_subscribe_bcast:{false}) {
+ setverdict(fail, __SCOPE__, "() failed");
+ Misc_Helpers.f_shutdown(__BFILE__, __LINE__);
+ }
+ }
+}
+function f_PFCPEM_unsubscribe_bcast() runs on PFCP_ConnHdlr {
+ PFCP_PROC.call(PFCPEM_unsubscribe_bcast:{-}) {
+ [] PFCP_PROC.getreply(PFCPEM_unsubscribe_bcast:{true});
+ [] PFCP_PROC.getreply(PFCPEM_unsubscribe_bcast:{false}) {
+ setverdict(fail, __SCOPE__, "() failed");
+ Misc_Helpers.f_shutdown(__BFILE__, __LINE__);
+ }
}
}

-function f_pfcp_get_recovery_timestamp()
+/* subscribe/unsubscribe for/from PDUs with the given SEID value */
+function f_PFCPEM_subscribe_seid(in OCT8 seid) runs on PFCP_ConnHdlr {
+ PFCP_PROC.call(PFCPEM_subscribe_seid:{seid, -}) {
+ [] PFCP_PROC.getreply(PFCPEM_subscribe_seid:{seid, true});
+ [] PFCP_PROC.getreply(PFCPEM_subscribe_seid:{seid, false}) {
+ setverdict(fail, __SCOPE__, "() failed");
+ Misc_Helpers.f_shutdown(__BFILE__, __LINE__);
+ }
+ }
+}
+function f_PFCPEM_unsubscribe_seid(in OCT8 seid) runs on PFCP_ConnHdlr {
+ PFCP_PROC.call(PFCPEM_unsubscribe_seid:{seid, -}) {
+ [] PFCP_PROC.getreply(PFCPEM_unsubscribe_seid:{seid, true});
+ [] PFCP_PROC.getreply(PFCPEM_unsubscribe_seid:{seid, false}) {
+ setverdict(fail, __SCOPE__, "() failed");
+ Misc_Helpers.f_shutdown(__BFILE__, __LINE__);
+ }
+ }
+}
+
+function f_PFCPEM_get_recovery_timestamp()
runs on PFCP_ConnHdlr return integer {
var integer rts;

diff --git a/s1gw/S1GW_ConnHdlr.ttcn b/s1gw/S1GW_ConnHdlr.ttcn
index 5b2e7bb..8d1a2b2 100644
--- a/s1gw/S1GW_ConnHdlr.ttcn
+++ b/s1gw/S1GW_ConnHdlr.ttcn
@@ -232,7 +232,9 @@
{
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),
@@ -264,8 +266,7 @@

function f_ConnHdlr_register_pfcp() runs on ConnHdlr
{
- f_pfcp_register();
- g_pfcp_recovery_timestamp := f_pfcp_get_recovery_timestamp();
+ g_pfcp_recovery_timestamp := f_PFCPEM_get_recovery_timestamp();

/* First ConnHdlr answers the AssocSetup: */
if (g_pars.idx != 0) {
@@ -561,6 +562,11 @@

function f_ConnHdlr_erab_setup_req(inout ERabList erabs)
runs on ConnHdlr {
+ const OCT8 c_SEID0 := '0000000000000000'O;
+
+ /* Subscribe for PFCP Session Establishment Request PDU(s), which are
+ * expected to have SEID set to 0, as per 3GPP TS 29.244, section 7.2.2.4.2. */
+ f_PFCPEM_subscribe_seid(c_SEID0);
log("eNB <- [S1GW <- MME]: E-RAB SETUP REQUEST");
f_ConnHdlr_tx_erab_setup_req(erabs);
for (var integer i := 0; i < lengthof(erabs); i := i + 1) {
@@ -568,9 +574,13 @@
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);
}
+ /* We no longer expect to receive PFCP Session Establishment Request PDU(s). */
+ f_PFCPEM_unsubscribe_seid(c_SEID0);
log("[eNB <- S1GW] <- MME: E-RAB SETUP REQUEST");
f_ConnHdlr_rx_erab_setup_req(erabs);
}
@@ -598,6 +608,8 @@
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);
}
f_ConnHdlr_rx_erab_release_cmd(erabs, cause);
}

To view, visit change 38207. To unsubscribe, or for help writing mail filters, visit settings.

Gerrit-MessageType: merged
Gerrit-Project: osmo-ttcn3-hacks
Gerrit-Branch: master
Gerrit-Change-Id: I25802471519fa297ad4cb2b056adaa6748b00af2
Gerrit-Change-Number: 38207
Gerrit-PatchSet: 5
Gerrit-Owner: fixeria <vyanitskiy@sysmocom.de>
Gerrit-Reviewer: Jenkins Builder
Gerrit-Reviewer: laforge <laforge@osmocom.org>
Gerrit-Reviewer: pespin <pespin@sysmocom.de>