blob: fb4ef08e0feab0b94d9fc7b743568b399df22981 [file] [log] [blame]
Neels Hofmeyr2d292742022-06-07 23:58:05 +02001module UPF_Tests {
2
3/* Integration Tests for OsmoUPF
4 * (C) 2022 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
5 * All rights reserved.
6 *
7 * Released under the terms of GNU General Public License, Version 2 or
8 * (at your option) any later version.
9 *
10 * SPDX-License-Identifier: GPL-2.0-or-later
11 *
12 * This test suite acts as a PFCP Control Plane Function to test OsmoUPF.
13 */
14
15import from Misc_Helpers all;
16import from General_Types all;
17import from Osmocom_Types all;
18import from IPL4asp_Types all;
19import from Native_Functions all;
20import from TCCConversion_Functions all;
21
22import from Osmocom_CTRL_Functions all;
23import from Osmocom_CTRL_Types all;
24import from Osmocom_CTRL_Adapter all;
25
26import from StatsD_Types all;
27import from StatsD_CodecPort all;
28import from StatsD_CodecPort_CtrlFunct all;
29import from StatsD_Checker all;
30
31import from Osmocom_VTY_Functions all;
32import from TELNETasp_PortType all;
33
34import from CPF_ConnectionHandler all;
35
36import from PFCP_Types all;
37import from PFCP_Emulation all;
38import from PFCP_Templates all;
39
40modulepar {
41 /* IP address at which the UPF can be reached */
42 charstring mp_pfcp_ip_upf := "127.0.0.1";
43 charstring mp_pfcp_ip_local := "127.0.0.2";
44
45 /* When testing with gtp mockup, actions will not show. */
46 boolean mp_verify_gtp_actions := false;
47}
48
49type component test_CT extends CTRL_Adapter_CT {
50 port PFCPEM_PT PFCP;
51
52 port TELNETasp_PT UPFVTY;
53
54 /* global test case guard timer (actual timeout value is set in f_init()) */
55 timer T_guard := 15.0;
56}
57
58/* global altstep for global guard timer; */
59altstep as_Tguard() runs on test_CT {
60 [] T_guard.timeout {
61 setverdict(fail, "Timeout of T_guard");
62 mtc.stop;
63 }
64}
65
66friend function f_logp(TELNETasp_PT pt, charstring log_msg)
67{
68 // log on TTCN3 log output
69 log(log_msg);
70 // log in stderr log
71 f_vty_transceive(pt, "logp lglobal notice TTCN3 f_logp(): " & log_msg);
72}
73
74private function f_str_split(charstring str, charstring delim := "\n") return ro_charstring
75{
76 var integer pos := 0;
77 var ro_charstring parts := {};
78 var integer delim_pos;
79 var integer end := lengthof(str);
80 while (pos < end) {
81 delim_pos := f_strstr(str, delim, pos);
82 if (delim_pos < 0) {
83 delim_pos := end;
84 }
85 parts := parts & { substr(str, pos, delim_pos - pos) };
86 pos := delim_pos + 1;
87 }
88 return parts;
89}
90
91private function f_get_name_val(out charstring val, charstring str, charstring name, charstring sep := ":", charstring delim := " ") return boolean {
92 var charstring labl := name & sep;
93 var integer namepos := f_strstr(str, labl);
94 if (namepos < 0) {
95 return false;
96 }
97 var integer valpos := namepos + lengthof(labl);
98 var integer valend := f_strstr(str, delim, valpos);
99 if (valend < 0) {
100 valend := lengthof(str);
101 }
102 val := substr(str, valpos, valend - valpos);
103 return true;
104}
105
106private function f_get_name_val_oct8(out OCT8 val, charstring str, charstring name) return boolean {
107 var charstring token;
108 if (not f_get_name_val(token, str, name, ":0x")) {
109 return false;
110 }
111 if (lengthof(token) > 16) {
112 log("token too long: ", name, " in ", str);
113 return false;
114 }
115 var charstring padded := substr("0000000000000000", 0, 16 - lengthof(token)) & token;
116 val := str2oct(padded);
117 return true;
118}
119
120private function f_get_name_val_oct4(out OCT4 val, charstring str, charstring name) return boolean {
121 var charstring token;
122 if (not f_get_name_val(token, str, name, ":0x")) {
123 return false;
124 }
125 if (lengthof(token) > 8) {
126 log("token too long: ", name, " in ", str);
127 return false;
128 }
129 var charstring padded := substr("00000000", 0, 8 - lengthof(token)) & token;
130 val := str2oct(padded);
131 return true;
132}
133
134private function f_get_name_val_int(out integer val, charstring str, charstring name) return boolean {
135 var charstring token;
136 if (not f_get_name_val(token, str, name)) {
137 return false;
138 }
139 val := str2int(token);
140 return true;
141}
142
143private function f_get_name_val_2int(out integer val1, out integer val2, charstring str, charstring name, charstring delim := ",") return boolean {
144 var charstring token;
145 if (not f_get_name_val(token, str, name)) {
146 return false;
147 }
148 var ro_charstring nrl := f_str_split(token, delim);
149 if (lengthof(nrl) != 2) {
150 return false;
151 }
152 val1 := str2int(nrl[0]);
153 val2 := str2int(nrl[1]);
154 return true;
155}
156
157/* A PFCP session as seen by the system under test, osmo-upf. up_seid is what osmo-upf sees as its local SEID
158 * ("SEID-l"). cp_seid is this tester's side's SEID, which osmo-upf sees as the remote SEID. */
159type record PFCP_session {
160 OCT8 up_seid,
161 OCT8 cp_seid,
162 GTP_Action gtp
163}
164
165type record GTP_Action {
Neels Hofmeyr366cdc72022-11-21 16:28:00 +0100166 /* kind = ("tunend"|"tunmap") */
Neels Hofmeyr2d292742022-06-07 23:58:05 +0200167 charstring kind,
168 charstring gtp_access_ip,
169 OCT4 teid_access_r,
170 OCT4 teid_access_l,
171 charstring core_ip,
172 charstring pfcp_peer,
173 OCT8 seid_l
174};
175
176type record of GTP_Action GTP_Action_List;
177
178private function f_parse_gtp_action(out GTP_Action ret, charstring str) return boolean {
179 var GTP_Action a;
180 if (not f_get_name_val(a.kind, str, "GTP")) {
181 return false;
182 }
183 if (not f_get_name_val(a.gtp_access_ip, str, "GTP-access")) {
184 return false;
185 }
186 if (not f_get_name_val_oct4(a.teid_access_r, str, "TEID-r")) {
187 return false;
188 }
189 if (not f_get_name_val_oct4(a.teid_access_l, str, "TEID-l")) {
190 return false;
191 }
192 if (not f_get_name_val(a.pfcp_peer, str, "PFCP-peer")) {
193 return false;
194 }
195 if (not f_get_name_val_oct8(a.seid_l, str, "SEID-l")) {
196 return false;
197 }
198 if (not f_get_name_val(a.core_ip, str, "IP-core")) {
199 return false;
200 }
201 ret := a;
202 return true;
203}
204
205private function f_vty_get_gtp_actions(TELNETasp_PT vty_pt) return GTP_Action_List {
206 var charstring gtp_str := f_vty_transceive_ret(vty_pt, "show gtp");
207 var ro_charstring lines := f_str_split(gtp_str, "\n");
208 var GTP_Action_List gtps := {};
209 for (var integer i := 0; i < lengthof(lines); i := i + 1) {
210 var charstring line := lines[i];
211 var GTP_Action a;
212 if (not f_parse_gtp_action(a, line)) {
213 continue;
214 }
215 gtps := gtps & { a };
216 }
217 log("GTP-actions: ", gtps);
218 return gtps;
219}
220
221private function f_find_gtp_action(GTP_Action_List actions, template GTP_Action find) return boolean {
222 for (var integer i := 0; i < lengthof(actions); i := i + 1) {
223 if (match(actions[i], find)) {
224 return true;
225 }
226 }
227 return false;
228}
229
230private function f_expect_gtp_action(GTP_Action_List actions, template GTP_Action expect) {
231 if (f_find_gtp_action(actions, expect)) {
232 log("VTY confirms: GTP action active: ", expect);
233 setverdict(pass);
234 return;
235 }
236 log("Expected to find ", expect, " in ", actions);
237 setverdict(fail, "on VTY, a GTP action failed to show as active");
238 mtc.stop;
239}
240
241private function f_expect_no_gtp_action(GTP_Action_List actions, template GTP_Action expect) {
242 if (f_find_gtp_action(actions, expect)) {
243 log("Expected to *not* find ", expect, " in ", actions);
244 setverdict(fail, "a GTP action failed to show as inactive");
245 mtc.stop;
246 }
247 log("VTY confirms: GTP action inactive: ", expect);
248 setverdict(pass);
249 return;
250}
251
252private function f_vty_expect_gtp_action(TELNETasp_PT vty_pt, template GTP_Action expect) {
253 if (not mp_verify_gtp_actions) {
254 /* In GTP mockup mode, GTP actions don't show on VTY. Cannot verify. */
255 setverdict(pass);
256 return;
257 }
258 var GTP_Action_List actions := f_vty_get_gtp_actions(vty_pt);
259 f_expect_gtp_action(actions, expect);
260}
261
262private function f_vty_expect_no_gtp_actions(TELNETasp_PT vty_pt) {
263 var GTP_Action_List actions := f_vty_get_gtp_actions(vty_pt);
264 if (lengthof(actions) > 0) {
265 setverdict(fail, "VTY says that there are still active GTP actions");
266 mtc.stop;
267 }
268 setverdict(pass);
269}
270
271type record PFCP_Session_Status {
272 charstring peer,
273 OCT8 seid_r,
274 OCT8 seid_l,
275 charstring state,
276 integer pdr_active_count,
277 integer pdr_count,
278 integer far_active_count,
279 integer far_count,
280 integer gtp_active_count
281};
282
283template PFCP_Session_Status PFCP_session_active := {
284 peer := ?,
285 seid_r := ?,
286 seid_l := ?,
287 state := "ESTABLISHED",
288 pdr_active_count := (1..99999),
289 pdr_count := (1..99999),
290 far_active_count := (1..99999),
291 far_count := (1..99999),
292 gtp_active_count := (1..99999)
293};
294
295template PFCP_Session_Status PFCP_session_inactive := {
296 peer := ?,
297 seid_r := ?,
298 seid_l := ?,
299 state := "ESTABLISHED",
300 pdr_active_count := 0,
301 pdr_count := (1..99999),
302 far_active_count := 0,
303 far_count := (1..99999),
304 gtp_active_count := 0
305};
306
307type record of PFCP_Session_Status PFCP_Session_Status_List;
308
309private function f_parse_session_status(out PFCP_Session_Status ret, charstring str) return boolean {
310 var PFCP_Session_Status st;
311 if (not f_get_name_val(st.peer, str, "peer")) {
312 return false;
313 }
314 if (not f_get_name_val_oct8(st.seid_l, str, "SEID-l")) {
315 return false;
316 }
317 f_get_name_val_oct8(st.seid_r, str, "SEID-r");
318 f_get_name_val(st.state, str, "state");
319
320 /* parse 'PDR-active:1/2' */
321 if (not f_get_name_val_2int(st.pdr_active_count, st.pdr_count, str, "PDR-active", "/")) {
322 return false;
323 }
324 /* parse 'FAR-active:1/2' */
325 if (not f_get_name_val_2int(st.far_active_count, st.far_count, str, "FAR-active", "/")) {
326 return false;
327 }
328
329 f_get_name_val_int(st.gtp_active_count, str, "GTP-active");
330 ret := st;
331 return true;
332}
333
334private function f_vty_get_sessions(TELNETasp_PT vty_pt) return PFCP_Session_Status_List {
335 var charstring sessions_str := f_vty_transceive_ret(vty_pt, "show session");
336 var ro_charstring lines := f_str_split(sessions_str, "\n");
337 var PFCP_Session_Status_List sessions := {};
338 for (var integer i := 0; i < lengthof(lines); i := i + 1) {
339 var charstring line := lines[i];
340 var PFCP_Session_Status st;
341 if (not f_parse_session_status(st, line)) {
342 continue;
343 }
344 sessions := sessions & { st };
345 }
346 log("Sessions: ", sessions);
347 return sessions;
348}
349
350private function f_vty_get_session_status(TELNETasp_PT vty_pt, PFCP_session s, out PFCP_Session_Status ret) return boolean {
351 var PFCP_Session_Status_List sessions := f_vty_get_sessions(vty_pt);
352 return f_get_session_status(sessions, s, ret);
353}
354
355private function f_get_session_status(PFCP_Session_Status_List sessions, PFCP_session s, out PFCP_Session_Status ret)
356return boolean {
357 var PFCP_Session_Status_List matches := {};
358 for (var integer i := 0; i < lengthof(sessions); i := i + 1) {
359 var PFCP_Session_Status st := sessions[i];
360 if (st.seid_l != s.up_seid) {
361 continue;
362 }
363 if (st.seid_r != s.cp_seid) {
364 continue;
365 }
366 matches := matches & { st };
367 }
368 if (lengthof(matches) < 1) {
369 log("no session with SEID-l = ", s.up_seid);
370 return false;
371 }
372 if (lengthof(matches) > 1) {
373 log("multiple sessions have ", s, ": ", matches);
374 return false;
375 }
376 ret := matches[0];
377 return true;
378}
379
380private function f_vty_expect_session_status(TELNETasp_PT vty_pt, PFCP_session s, template PFCP_Session_Status expect_st) {
381 var PFCP_Session_Status st;
382 if (not f_vty_get_session_status(vty_pt, s, st)) {
383 log("Session ", s, " not found in VTY session list");
384 setverdict(fail, "Session not found in VTY list");
385 mtc.stop;
386 }
387 log("Session ", s, " status: ", st);
388 if (not match(st, expect_st)) {
389 log("ERROR: Session ", st, " does not match ", expect_st);
390 setverdict(fail, "VTY shows unexpected state of PFCP session");
391 mtc.stop;
392 }
393
394 setverdict(pass);
395}
396
397private function f_vty_expect_session_active(TELNETasp_PT vty_pt, PFCP_session s)
398{
399 f_vty_expect_session_status(vty_pt, s, PFCP_session_active);
400 f_vty_expect_gtp_action(vty_pt, s.gtp);
401 setverdict(pass);
402}
403
404private function f_vty_expect_no_active_sessions(TELNETasp_PT vty_pt) {
405 var PFCP_Session_Status_List stl := f_vty_get_sessions(vty_pt);
406 var integer active := 0;
407 for (var integer i := 0; i < lengthof(stl); i := i + 1) {
408 if (match(stl[i], PFCP_session_active)) {
409 log("Active session: ", stl[i]);
410 active := active + 1;
411 }
412 }
413 if (active > 0) {
414 setverdict(fail, "There are still active sessions");
415 mtc.stop;
416 }
417 setverdict(pass);
418}
419
420function f_init_vty(charstring id := "foo") runs on test_CT {
421 if (UPFVTY.checkstate("Mapped")) {
422 /* skip initialization if already executed once */
423 return;
424 }
425 map(self:UPFVTY, system:UPFVTY);
426 f_vty_set_prompts(UPFVTY);
427 f_vty_transceive(UPFVTY, "enable");
428}
429
430/* global initialization function */
431function f_init(float guard_timeout := 30.0) runs on test_CT {
432 var integer bssap_idx;
433
434 T_guard.start(guard_timeout);
435 activate(as_Tguard());
436
437 f_init_vty("VirtCPF");
438}
439
440friend function f_shutdown_helper() runs on test_CT {
441 all component.stop;
442 setverdict(pass);
443 mtc.stop;
444}
445
446private function f_gen_test_hdlr_pars() runs on test_CT return TestHdlrParams {
447 var TestHdlrParams pars := valueof(t_def_TestHdlrPars);
448 pars.remote_upf_addr := mp_pfcp_ip_upf;
449 pars.local_addr := mp_pfcp_ip_local;
450 pars.local_node_id := valueof(ts_PFCP_Node_ID_ipv4(f_inet_addr(mp_pfcp_ip_local)));
451 return pars;
452}
453
454type function void_fn(charstring id) runs on CPF_ConnHdlr;
455
456function f_start_handler_create(TestHdlrParams pars)
457runs on test_CT return CPF_ConnHdlr {
458 var charstring id := testcasename();
459 var CPF_ConnHdlr vc_conn;
460 vc_conn := CPF_ConnHdlr.create(id);
461 return vc_conn;
462}
463
464function f_start_handler_run(CPF_ConnHdlr vc_conn, void_fn fn, TestHdlrParams pars)
465runs on test_CT return CPF_ConnHdlr {
466 var charstring id := testcasename();
467 /* Emit a marker to appear in the SUT's own logging output */
468 f_logp(UPFVTY, id & "() start");
469 vc_conn.start(f_handler_init(fn, id, pars));
470 return vc_conn;
471}
472
473function f_start_handler(void_fn fn, template (omit) TestHdlrParams pars_tmpl := omit)
474runs on test_CT return CPF_ConnHdlr {
475 var TestHdlrParams pars;
476 if (isvalue(pars_tmpl)) {
477 pars := valueof(pars_tmpl);
478 } else {
479 pars := valueof(f_gen_test_hdlr_pars());
480 }
481 return f_start_handler_run(f_start_handler_create(pars), fn, pars);
482}
483
484/* first function inside ConnHdlr component; sets g_pars + starts function */
485private function f_handler_init(void_fn fn, charstring id, TestHdlrParams pars)
486runs on CPF_ConnHdlr {
487 f_CPF_ConnHdlr_init(id, pars);
488 fn.apply(id);
489}
490
491/* Run a PFCP Association procedure */
492private function f_assoc_setup() runs on CPF_ConnHdlr {
493 PFCP.send(ts_PFCP_Assoc_Setup_Req(g_pars.local_node_id, g_recovery_timestamp));
494 PFCP.receive(tr_PFCP_Assoc_Setup_Resp(cause := tr_PFCP_Cause(REQUEST_ACCEPTED)));
495}
496
497/* Release a PFCP Association */
498private function f_assoc_release() runs on CPF_ConnHdlr {
499 PFCP.send(ts_PFCP_Assoc_Release_Req(g_pars.local_node_id));
500 PFCP.receive(tr_PFCP_Assoc_Release_Resp(cause := tr_PFCP_Cause(REQUEST_ACCEPTED)));
501}
502
503type record PFCP_Ruleset {
504 Create_PDR_list pdr,
505 Create_FAR_list far
506};
507
508/* Add to r a rule set that does GTP decapsulation (half of encapsulation/decapsulation) */
509private function f_ruleset_add_GTP_decaps(inout PFCP_Ruleset r,
510 template F_TEID local_f_teid := omit) {
511 var integer pdr_id := lengthof(r.pdr) + 1;
512 var integer far_id := lengthof(r.far) + 1;
513
514 r.pdr := r.pdr & {
515 valueof(
516 ts_PFCP_Create_PDR(
517 pdr_id,
518 ts_PFCP_PDI(
519 ACCESS,
520 local_F_TEID := local_f_teid),
521 ts_PFCP_Outer_Header_Removal(GTP_U_UDP_IPV4),
522 far_id
523 )
524 )
525 };
526 r.far := r.far & {
527 valueof(
528 ts_PFCP_Create_FAR(
529 far_id,
530 ts_PFCP_Apply_Action_FORW(),
531 valueof(ts_PFCP_Forwarding_Parameters(CORE))
532 )
533 )
534 };
535}
536
537/* Add to r a rule set that does GTP encapsulation (half of encapsulation/decapsulation) */
538private function f_ruleset_add_GTP_encaps(inout PFCP_Ruleset r,
539 charstring ue_addr_v4 := "192.168.23.42",
540 OCT4 remote_teid,
541 charstring gtp_dest_addr_v4) {
542
543 var integer pdr_id := lengthof(r.pdr) + 1;
544 var integer far_id := lengthof(r.far) + 1;
545
546 r.pdr := r.pdr & {
547 valueof(
548 ts_PFCP_Create_PDR(
549 pdr_id,
550 ts_PFCP_PDI(
551 CORE,
552 ue_addr_v4 := ts_PFCP_UE_IP_Address_v4(ue_addr_v4, is_destination := true)
553 ),
554 far_id := far_id
555 )
556 )
557 };
558 r.far := r.far & {
559 valueof(
560 ts_PFCP_Create_FAR(
561 far_id,
562 ts_PFCP_Apply_Action_FORW(),
563 valueof(ts_PFCP_Forwarding_Parameters(
564 ACCESS,
565 ts_PFCP_Outer_Header_Creation_GTP_ipv4(
566 remote_teid,
567 gtp_dest_addr_v4)
568 ))
569 )
570 )
571 };
572}
573
574/* Return two PDR+FAR rulesets that involve a src=CP-Function. Such rulesets are emitted by certain third party CPF, and
575 * osmo-upf should ACK the creation but ignore the rules (no-op). This function models rulesets seen in the field, so we
576 * can confirm that osmo-upf ACKs and ignores. */
577private function f_ruleset_noop() return PFCP_Ruleset
578{
579 var PFCP_Ruleset r := { {}, {} };
580 var integer pdr_id := lengthof(r.pdr) + 1;
581 var integer far_id := lengthof(r.far) + 1;
582
583 r.pdr := r.pdr & {
584 valueof(
585 ts_PFCP_Create_PDR(
586 pdr_id,
587 ts_PFCP_PDI(
588 CP_FUNCTION,
589 local_F_TEID := ts_PFCP_F_TEID_choose_v4('17'O)),
590 ts_PFCP_Outer_Header_Removal(GTP_U_UDP_IPV4),
591 far_id
592 )
593 )
594 };
595 r.far := r.far & {
596 valueof(
597 ts_PFCP_Create_FAR(
598 far_id,
599 ts_PFCP_Apply_Action_FORW(),
600 valueof(ts_PFCP_Forwarding_Parameters(ACCESS))
601 )
602 )
603 };
604
605 /* And another one (sic) */
606 pdr_id := lengthof(r.pdr) + 1;
607 far_id := lengthof(r.far) + 1;
608
609 r.pdr := r.pdr & {
610 valueof(
611 ts_PFCP_Create_PDR(
612 pdr_id,
613 ts_PFCP_PDI(
614 CP_FUNCTION,
615 local_F_TEID := ts_PFCP_F_TEID_choose_v4('2a'O)),
616 far_id := far_id
617 )
618 )
619 };
620 r.far := r.far & {
621 valueof(
622 ts_PFCP_Create_FAR(
623 far_id,
624 ts_PFCP_Apply_Action_FORW(),
625 valueof(ts_PFCP_Forwarding_Parameters(ACCESS))
626 )
627 )
628 };
629 return r;
630}
631
632/* Return a rule set that does GTP encapsulation/decapsulation */
Neels Hofmeyr366cdc72022-11-21 16:28:00 +0100633private function f_ruleset_tunend(GTP_Action gtp) return PFCP_Ruleset
Neels Hofmeyr2d292742022-06-07 23:58:05 +0200634{
635 var PFCP_Ruleset rules := { {}, {} };
636 f_ruleset_add_GTP_decaps(rules, ts_PFCP_F_TEID_ipv4(gtp.teid_access_l, gtp.gtp_access_ip));
637 f_ruleset_add_GTP_encaps(rules, gtp.core_ip, gtp.teid_access_r, gtp.gtp_access_ip);
638 return rules;
639}
640
641/* Run a PFCP Session Establishment procedure */
642private function f_session_est(inout PFCP_session s, PFCP_Ruleset rules) runs on CPF_ConnHdlr {
643
644 PFCP.send(ts_PFCP_Session_Est_Req(g_pars.local_addr, s.cp_seid, rules.pdr, rules.far));
645
646 var PDU_PFCP pfcp;
647 PFCP.receive(tr_PFCP_Session_Est_Resp(s.cp_seid)) -> value pfcp;
648 s.up_seid := pfcp.message_body.pfcp_session_establishment_response.UP_F_SEID.seid;
649 s.gtp.seid_l := s.up_seid;
650 log("established PFCP session: ", s);
651}
652
653private function f_create_PFCP_session() runs on CPF_ConnHdlr return PFCP_session
654{
655 var PFCP_session s := {
656 up_seid := -,
657 cp_seid := f_next_seid(),
658 gtp := {
Neels Hofmeyr366cdc72022-11-21 16:28:00 +0100659 kind := "tunend",
Neels Hofmeyr2d292742022-06-07 23:58:05 +0200660 gtp_access_ip := "127.0.0.2",
661 teid_access_r := f_next_remote_teid(),
662 teid_access_l := f_next_local_teid(),
663 core_ip := f_next_ue_addr(),
664 pfcp_peer := g_pars.local_addr,
665 seid_l := '0000000000000000'O
666 }
667 };
668 return s;
669}
670
671/* Do a PFCP Session Establishment with default values (see f_create_PFCP_session()) */
672private function f_session_est_default() runs on CPF_ConnHdlr return PFCP_session
673{
674 var PFCP_session s := f_create_PFCP_session();
Neels Hofmeyr366cdc72022-11-21 16:28:00 +0100675 f_session_est(s, f_ruleset_tunend(s.gtp));
Neels Hofmeyr2d292742022-06-07 23:58:05 +0200676 return s;
677}
678
679private function f_session_del(PFCP_session s) runs on CPF_ConnHdlr {
680 PFCP.send(ts_PFCP_Session_Del_Req(s.up_seid));
681 PFCP.receive(tr_PFCP_Session_Del_Resp(s.cp_seid));
682}
683
684private function f_tc_assoc(charstring id) runs on CPF_ConnHdlr {
685 f_assoc_setup();
686 f_assoc_release();
687 setverdict(pass);
688}
689
690/* Verify that the CPF can send a Node-ID of the IPv4 type */
691testcase TC_assoc_node_id_v4() runs on test_CT {
692 var CPF_ConnHdlr vc_conn;
693
694 f_init(guard_timeout := 5.0);
695 vc_conn := f_start_handler(refers(f_tc_assoc));
696 vc_conn.done;
697 f_shutdown_helper();
698}
699
700/* Verify that the CPF can send a Node-ID of the FQDN type */
701testcase TC_assoc_node_id_fqdn() runs on test_CT {
702 var CPF_ConnHdlr vc_conn;
703 var TestHdlrParams pars := f_gen_test_hdlr_pars();
704
705 pars.local_node_id := valueof(ts_PFCP_Node_ID_fqdn("\7example\3com"));
706
707 f_init(guard_timeout := 5.0);
708 vc_conn := f_start_handler(refers(f_tc_assoc), pars);
709 vc_conn.done;
710 f_shutdown_helper();
711}
712
713/* Verify PFCP Session Establishment and Deletion */
714private function f_tc_session_est(charstring id) runs on CPF_ConnHdlr {
715 f_assoc_setup();
716 var PFCP_session s := f_session_est_default();
717 f_sleep(1.0);
718 f_vty_expect_session_active(UPFVTY, s);
719 f_session_del(s);
720 f_vty_expect_no_active_sessions(UPFVTY);
721 f_vty_expect_no_gtp_actions(UPFVTY);
722 f_assoc_release();
723 setverdict(pass);
724}
725testcase TC_session_est() runs on test_CT {
726 var CPF_ConnHdlr vc_conn;
727
728 f_init(guard_timeout := 15.0);
729 vc_conn := f_start_handler(refers(f_tc_session_est));
730 vc_conn.done;
731 f_shutdown_helper();
732}
733
734/* Verify that releasing a PFCP Association also releases all its sessions and GTP actions. */
735private function f_tc_session_term_by_assoc_rel(charstring id) runs on CPF_ConnHdlr {
736 f_assoc_setup();
737 var PFCP_session s := f_session_est_default();
738 f_sleep(1.0);
739 f_vty_expect_session_active(UPFVTY, s);
740 f_assoc_release();
741 f_vty_expect_no_active_sessions(UPFVTY);
742 f_vty_expect_no_gtp_actions(UPFVTY);
743 setverdict(pass);
744}
745testcase TC_session_term_by_assoc_rel() runs on test_CT {
746 var CPF_ConnHdlr vc_conn;
747
748 f_init(guard_timeout := 15.0);
749 vc_conn := f_start_handler(refers(f_tc_session_term_by_assoc_rel));
750 vc_conn.done;
751 f_shutdown_helper();
752}
753
754/* Verify that PFCP Sessions with a src-interface other than ACCESS or CORE are ACKed by osmo-upf but have no effect. */
755private function f_tc_session_est_noop(charstring id) runs on CPF_ConnHdlr {
756 f_assoc_setup();
757 var PFCP_session s := f_create_PFCP_session();
758 f_session_est(s, f_ruleset_noop());
759
760 f_sleep(1.0);
761 f_vty_expect_session_status(UPFVTY, s, PFCP_session_inactive);
762
763 f_session_del(s);
764 f_vty_expect_no_active_sessions(UPFVTY);
765 f_vty_expect_no_gtp_actions(UPFVTY);
766 f_assoc_release();
767 setverdict(pass);
768}
769testcase TC_session_est_noop() runs on test_CT {
770 var CPF_ConnHdlr vc_conn;
771
772 f_init(guard_timeout := 15.0);
773 vc_conn := f_start_handler(refers(f_tc_session_est_noop));
774 vc_conn.done;
775 f_shutdown_helper();
776}
777
778control {
779 execute( TC_assoc_node_id_v4() );
780 execute( TC_assoc_node_id_fqdn() );
781 execute( TC_session_est() );
782 execute( TC_session_term_by_assoc_rel() );
783 execute( TC_session_est_noop() );
784}
785
786}