blob: ca1a0f61516b1c083583015f068c966ea6cc48ea [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 {
166 charstring kind,
167 charstring gtp_access_ip,
168 OCT4 teid_access_r,
169 OCT4 teid_access_l,
170 charstring core_ip,
171 charstring pfcp_peer,
172 OCT8 seid_l
173};
174
175type record of GTP_Action GTP_Action_List;
176
177private function f_parse_gtp_action(out GTP_Action ret, charstring str) return boolean {
178 var GTP_Action a;
179 if (not f_get_name_val(a.kind, str, "GTP")) {
180 return false;
181 }
182 if (not f_get_name_val(a.gtp_access_ip, str, "GTP-access")) {
183 return false;
184 }
185 if (not f_get_name_val_oct4(a.teid_access_r, str, "TEID-r")) {
186 return false;
187 }
188 if (not f_get_name_val_oct4(a.teid_access_l, str, "TEID-l")) {
189 return false;
190 }
191 if (not f_get_name_val(a.pfcp_peer, str, "PFCP-peer")) {
192 return false;
193 }
194 if (not f_get_name_val_oct8(a.seid_l, str, "SEID-l")) {
195 return false;
196 }
197 if (not f_get_name_val(a.core_ip, str, "IP-core")) {
198 return false;
199 }
200 ret := a;
201 return true;
202}
203
204private function f_vty_get_gtp_actions(TELNETasp_PT vty_pt) return GTP_Action_List {
205 var charstring gtp_str := f_vty_transceive_ret(vty_pt, "show gtp");
206 var ro_charstring lines := f_str_split(gtp_str, "\n");
207 var GTP_Action_List gtps := {};
208 for (var integer i := 0; i < lengthof(lines); i := i + 1) {
209 var charstring line := lines[i];
210 var GTP_Action a;
211 if (not f_parse_gtp_action(a, line)) {
212 continue;
213 }
214 gtps := gtps & { a };
215 }
216 log("GTP-actions: ", gtps);
217 return gtps;
218}
219
220private function f_find_gtp_action(GTP_Action_List actions, template GTP_Action find) return boolean {
221 for (var integer i := 0; i < lengthof(actions); i := i + 1) {
222 if (match(actions[i], find)) {
223 return true;
224 }
225 }
226 return false;
227}
228
229private function f_expect_gtp_action(GTP_Action_List actions, template GTP_Action expect) {
230 if (f_find_gtp_action(actions, expect)) {
231 log("VTY confirms: GTP action active: ", expect);
232 setverdict(pass);
233 return;
234 }
235 log("Expected to find ", expect, " in ", actions);
236 setverdict(fail, "on VTY, a GTP action failed to show as active");
237 mtc.stop;
238}
239
240private function f_expect_no_gtp_action(GTP_Action_List actions, template GTP_Action expect) {
241 if (f_find_gtp_action(actions, expect)) {
242 log("Expected to *not* find ", expect, " in ", actions);
243 setverdict(fail, "a GTP action failed to show as inactive");
244 mtc.stop;
245 }
246 log("VTY confirms: GTP action inactive: ", expect);
247 setverdict(pass);
248 return;
249}
250
251private function f_vty_expect_gtp_action(TELNETasp_PT vty_pt, template GTP_Action expect) {
252 if (not mp_verify_gtp_actions) {
253 /* In GTP mockup mode, GTP actions don't show on VTY. Cannot verify. */
254 setverdict(pass);
255 return;
256 }
257 var GTP_Action_List actions := f_vty_get_gtp_actions(vty_pt);
258 f_expect_gtp_action(actions, expect);
259}
260
261private function f_vty_expect_no_gtp_actions(TELNETasp_PT vty_pt) {
262 var GTP_Action_List actions := f_vty_get_gtp_actions(vty_pt);
263 if (lengthof(actions) > 0) {
264 setverdict(fail, "VTY says that there are still active GTP actions");
265 mtc.stop;
266 }
267 setverdict(pass);
268}
269
270type record PFCP_Session_Status {
271 charstring peer,
272 OCT8 seid_r,
273 OCT8 seid_l,
274 charstring state,
275 integer pdr_active_count,
276 integer pdr_count,
277 integer far_active_count,
278 integer far_count,
279 integer gtp_active_count
280};
281
282template PFCP_Session_Status PFCP_session_active := {
283 peer := ?,
284 seid_r := ?,
285 seid_l := ?,
286 state := "ESTABLISHED",
287 pdr_active_count := (1..99999),
288 pdr_count := (1..99999),
289 far_active_count := (1..99999),
290 far_count := (1..99999),
291 gtp_active_count := (1..99999)
292};
293
294template PFCP_Session_Status PFCP_session_inactive := {
295 peer := ?,
296 seid_r := ?,
297 seid_l := ?,
298 state := "ESTABLISHED",
299 pdr_active_count := 0,
300 pdr_count := (1..99999),
301 far_active_count := 0,
302 far_count := (1..99999),
303 gtp_active_count := 0
304};
305
306type record of PFCP_Session_Status PFCP_Session_Status_List;
307
308private function f_parse_session_status(out PFCP_Session_Status ret, charstring str) return boolean {
309 var PFCP_Session_Status st;
310 if (not f_get_name_val(st.peer, str, "peer")) {
311 return false;
312 }
313 if (not f_get_name_val_oct8(st.seid_l, str, "SEID-l")) {
314 return false;
315 }
316 f_get_name_val_oct8(st.seid_r, str, "SEID-r");
317 f_get_name_val(st.state, str, "state");
318
319 /* parse 'PDR-active:1/2' */
320 if (not f_get_name_val_2int(st.pdr_active_count, st.pdr_count, str, "PDR-active", "/")) {
321 return false;
322 }
323 /* parse 'FAR-active:1/2' */
324 if (not f_get_name_val_2int(st.far_active_count, st.far_count, str, "FAR-active", "/")) {
325 return false;
326 }
327
328 f_get_name_val_int(st.gtp_active_count, str, "GTP-active");
329 ret := st;
330 return true;
331}
332
333private function f_vty_get_sessions(TELNETasp_PT vty_pt) return PFCP_Session_Status_List {
334 var charstring sessions_str := f_vty_transceive_ret(vty_pt, "show session");
335 var ro_charstring lines := f_str_split(sessions_str, "\n");
336 var PFCP_Session_Status_List sessions := {};
337 for (var integer i := 0; i < lengthof(lines); i := i + 1) {
338 var charstring line := lines[i];
339 var PFCP_Session_Status st;
340 if (not f_parse_session_status(st, line)) {
341 continue;
342 }
343 sessions := sessions & { st };
344 }
345 log("Sessions: ", sessions);
346 return sessions;
347}
348
349private function f_vty_get_session_status(TELNETasp_PT vty_pt, PFCP_session s, out PFCP_Session_Status ret) return boolean {
350 var PFCP_Session_Status_List sessions := f_vty_get_sessions(vty_pt);
351 return f_get_session_status(sessions, s, ret);
352}
353
354private function f_get_session_status(PFCP_Session_Status_List sessions, PFCP_session s, out PFCP_Session_Status ret)
355return boolean {
356 var PFCP_Session_Status_List matches := {};
357 for (var integer i := 0; i < lengthof(sessions); i := i + 1) {
358 var PFCP_Session_Status st := sessions[i];
359 if (st.seid_l != s.up_seid) {
360 continue;
361 }
362 if (st.seid_r != s.cp_seid) {
363 continue;
364 }
365 matches := matches & { st };
366 }
367 if (lengthof(matches) < 1) {
368 log("no session with SEID-l = ", s.up_seid);
369 return false;
370 }
371 if (lengthof(matches) > 1) {
372 log("multiple sessions have ", s, ": ", matches);
373 return false;
374 }
375 ret := matches[0];
376 return true;
377}
378
379private function f_vty_expect_session_status(TELNETasp_PT vty_pt, PFCP_session s, template PFCP_Session_Status expect_st) {
380 var PFCP_Session_Status st;
381 if (not f_vty_get_session_status(vty_pt, s, st)) {
382 log("Session ", s, " not found in VTY session list");
383 setverdict(fail, "Session not found in VTY list");
384 mtc.stop;
385 }
386 log("Session ", s, " status: ", st);
387 if (not match(st, expect_st)) {
388 log("ERROR: Session ", st, " does not match ", expect_st);
389 setverdict(fail, "VTY shows unexpected state of PFCP session");
390 mtc.stop;
391 }
392
393 setverdict(pass);
394}
395
396private function f_vty_expect_session_active(TELNETasp_PT vty_pt, PFCP_session s)
397{
398 f_vty_expect_session_status(vty_pt, s, PFCP_session_active);
399 f_vty_expect_gtp_action(vty_pt, s.gtp);
400 setverdict(pass);
401}
402
403private function f_vty_expect_no_active_sessions(TELNETasp_PT vty_pt) {
404 var PFCP_Session_Status_List stl := f_vty_get_sessions(vty_pt);
405 var integer active := 0;
406 for (var integer i := 0; i < lengthof(stl); i := i + 1) {
407 if (match(stl[i], PFCP_session_active)) {
408 log("Active session: ", stl[i]);
409 active := active + 1;
410 }
411 }
412 if (active > 0) {
413 setverdict(fail, "There are still active sessions");
414 mtc.stop;
415 }
416 setverdict(pass);
417}
418
419function f_init_vty(charstring id := "foo") runs on test_CT {
420 if (UPFVTY.checkstate("Mapped")) {
421 /* skip initialization if already executed once */
422 return;
423 }
424 map(self:UPFVTY, system:UPFVTY);
425 f_vty_set_prompts(UPFVTY);
426 f_vty_transceive(UPFVTY, "enable");
427}
428
429/* global initialization function */
430function f_init(float guard_timeout := 30.0) runs on test_CT {
431 var integer bssap_idx;
432
433 T_guard.start(guard_timeout);
434 activate(as_Tguard());
435
436 f_init_vty("VirtCPF");
437}
438
439friend function f_shutdown_helper() runs on test_CT {
440 all component.stop;
441 setverdict(pass);
442 mtc.stop;
443}
444
445private function f_gen_test_hdlr_pars() runs on test_CT return TestHdlrParams {
446 var TestHdlrParams pars := valueof(t_def_TestHdlrPars);
447 pars.remote_upf_addr := mp_pfcp_ip_upf;
448 pars.local_addr := mp_pfcp_ip_local;
449 pars.local_node_id := valueof(ts_PFCP_Node_ID_ipv4(f_inet_addr(mp_pfcp_ip_local)));
450 return pars;
451}
452
453type function void_fn(charstring id) runs on CPF_ConnHdlr;
454
455function f_start_handler_create(TestHdlrParams pars)
456runs on test_CT return CPF_ConnHdlr {
457 var charstring id := testcasename();
458 var CPF_ConnHdlr vc_conn;
459 vc_conn := CPF_ConnHdlr.create(id);
460 return vc_conn;
461}
462
463function f_start_handler_run(CPF_ConnHdlr vc_conn, void_fn fn, TestHdlrParams pars)
464runs on test_CT return CPF_ConnHdlr {
465 var charstring id := testcasename();
466 /* Emit a marker to appear in the SUT's own logging output */
467 f_logp(UPFVTY, id & "() start");
468 vc_conn.start(f_handler_init(fn, id, pars));
469 return vc_conn;
470}
471
472function f_start_handler(void_fn fn, template (omit) TestHdlrParams pars_tmpl := omit)
473runs on test_CT return CPF_ConnHdlr {
474 var TestHdlrParams pars;
475 if (isvalue(pars_tmpl)) {
476 pars := valueof(pars_tmpl);
477 } else {
478 pars := valueof(f_gen_test_hdlr_pars());
479 }
480 return f_start_handler_run(f_start_handler_create(pars), fn, pars);
481}
482
483/* first function inside ConnHdlr component; sets g_pars + starts function */
484private function f_handler_init(void_fn fn, charstring id, TestHdlrParams pars)
485runs on CPF_ConnHdlr {
486 f_CPF_ConnHdlr_init(id, pars);
487 fn.apply(id);
488}
489
490/* Run a PFCP Association procedure */
491private function f_assoc_setup() runs on CPF_ConnHdlr {
492 PFCP.send(ts_PFCP_Assoc_Setup_Req(g_pars.local_node_id, g_recovery_timestamp));
493 PFCP.receive(tr_PFCP_Assoc_Setup_Resp(cause := tr_PFCP_Cause(REQUEST_ACCEPTED)));
494}
495
496/* Release a PFCP Association */
497private function f_assoc_release() runs on CPF_ConnHdlr {
498 PFCP.send(ts_PFCP_Assoc_Release_Req(g_pars.local_node_id));
499 PFCP.receive(tr_PFCP_Assoc_Release_Resp(cause := tr_PFCP_Cause(REQUEST_ACCEPTED)));
500}
501
502type record PFCP_Ruleset {
503 Create_PDR_list pdr,
504 Create_FAR_list far
505};
506
507/* Add to r a rule set that does GTP decapsulation (half of encapsulation/decapsulation) */
508private function f_ruleset_add_GTP_decaps(inout PFCP_Ruleset r,
509 template F_TEID local_f_teid := omit) {
510 var integer pdr_id := lengthof(r.pdr) + 1;
511 var integer far_id := lengthof(r.far) + 1;
512
513 r.pdr := r.pdr & {
514 valueof(
515 ts_PFCP_Create_PDR(
516 pdr_id,
517 ts_PFCP_PDI(
518 ACCESS,
519 local_F_TEID := local_f_teid),
520 ts_PFCP_Outer_Header_Removal(GTP_U_UDP_IPV4),
521 far_id
522 )
523 )
524 };
525 r.far := r.far & {
526 valueof(
527 ts_PFCP_Create_FAR(
528 far_id,
529 ts_PFCP_Apply_Action_FORW(),
530 valueof(ts_PFCP_Forwarding_Parameters(CORE))
531 )
532 )
533 };
534}
535
536/* Add to r a rule set that does GTP encapsulation (half of encapsulation/decapsulation) */
537private function f_ruleset_add_GTP_encaps(inout PFCP_Ruleset r,
538 charstring ue_addr_v4 := "192.168.23.42",
539 OCT4 remote_teid,
540 charstring gtp_dest_addr_v4) {
541
542 var integer pdr_id := lengthof(r.pdr) + 1;
543 var integer far_id := lengthof(r.far) + 1;
544
545 r.pdr := r.pdr & {
546 valueof(
547 ts_PFCP_Create_PDR(
548 pdr_id,
549 ts_PFCP_PDI(
550 CORE,
551 ue_addr_v4 := ts_PFCP_UE_IP_Address_v4(ue_addr_v4, is_destination := true)
552 ),
553 far_id := far_id
554 )
555 )
556 };
557 r.far := r.far & {
558 valueof(
559 ts_PFCP_Create_FAR(
560 far_id,
561 ts_PFCP_Apply_Action_FORW(),
562 valueof(ts_PFCP_Forwarding_Parameters(
563 ACCESS,
564 ts_PFCP_Outer_Header_Creation_GTP_ipv4(
565 remote_teid,
566 gtp_dest_addr_v4)
567 ))
568 )
569 )
570 };
571}
572
573/* Return two PDR+FAR rulesets that involve a src=CP-Function. Such rulesets are emitted by certain third party CPF, and
574 * osmo-upf should ACK the creation but ignore the rules (no-op). This function models rulesets seen in the field, so we
575 * can confirm that osmo-upf ACKs and ignores. */
576private function f_ruleset_noop() return PFCP_Ruleset
577{
578 var PFCP_Ruleset r := { {}, {} };
579 var integer pdr_id := lengthof(r.pdr) + 1;
580 var integer far_id := lengthof(r.far) + 1;
581
582 r.pdr := r.pdr & {
583 valueof(
584 ts_PFCP_Create_PDR(
585 pdr_id,
586 ts_PFCP_PDI(
587 CP_FUNCTION,
588 local_F_TEID := ts_PFCP_F_TEID_choose_v4('17'O)),
589 ts_PFCP_Outer_Header_Removal(GTP_U_UDP_IPV4),
590 far_id
591 )
592 )
593 };
594 r.far := r.far & {
595 valueof(
596 ts_PFCP_Create_FAR(
597 far_id,
598 ts_PFCP_Apply_Action_FORW(),
599 valueof(ts_PFCP_Forwarding_Parameters(ACCESS))
600 )
601 )
602 };
603
604 /* And another one (sic) */
605 pdr_id := lengthof(r.pdr) + 1;
606 far_id := lengthof(r.far) + 1;
607
608 r.pdr := r.pdr & {
609 valueof(
610 ts_PFCP_Create_PDR(
611 pdr_id,
612 ts_PFCP_PDI(
613 CP_FUNCTION,
614 local_F_TEID := ts_PFCP_F_TEID_choose_v4('2a'O)),
615 far_id := far_id
616 )
617 )
618 };
619 r.far := r.far & {
620 valueof(
621 ts_PFCP_Create_FAR(
622 far_id,
623 ts_PFCP_Apply_Action_FORW(),
624 valueof(ts_PFCP_Forwarding_Parameters(ACCESS))
625 )
626 )
627 };
628 return r;
629}
630
631/* Return a rule set that does GTP encapsulation/decapsulation */
632private function f_ruleset_endecaps(GTP_Action gtp) return PFCP_Ruleset
633{
634 var PFCP_Ruleset rules := { {}, {} };
635 f_ruleset_add_GTP_decaps(rules, ts_PFCP_F_TEID_ipv4(gtp.teid_access_l, gtp.gtp_access_ip));
636 f_ruleset_add_GTP_encaps(rules, gtp.core_ip, gtp.teid_access_r, gtp.gtp_access_ip);
637 return rules;
638}
639
640/* Run a PFCP Session Establishment procedure */
641private function f_session_est(inout PFCP_session s, PFCP_Ruleset rules) runs on CPF_ConnHdlr {
642
643 PFCP.send(ts_PFCP_Session_Est_Req(g_pars.local_addr, s.cp_seid, rules.pdr, rules.far));
644
645 var PDU_PFCP pfcp;
646 PFCP.receive(tr_PFCP_Session_Est_Resp(s.cp_seid)) -> value pfcp;
647 s.up_seid := pfcp.message_body.pfcp_session_establishment_response.UP_F_SEID.seid;
648 s.gtp.seid_l := s.up_seid;
649 log("established PFCP session: ", s);
650}
651
652private function f_create_PFCP_session() runs on CPF_ConnHdlr return PFCP_session
653{
654 var PFCP_session s := {
655 up_seid := -,
656 cp_seid := f_next_seid(),
657 gtp := {
658 kind := "endecaps",
659 gtp_access_ip := "127.0.0.2",
660 teid_access_r := f_next_remote_teid(),
661 teid_access_l := f_next_local_teid(),
662 core_ip := f_next_ue_addr(),
663 pfcp_peer := g_pars.local_addr,
664 seid_l := '0000000000000000'O
665 }
666 };
667 return s;
668}
669
670/* Do a PFCP Session Establishment with default values (see f_create_PFCP_session()) */
671private function f_session_est_default() runs on CPF_ConnHdlr return PFCP_session
672{
673 var PFCP_session s := f_create_PFCP_session();
674 f_session_est(s, f_ruleset_endecaps(s.gtp));
675 return s;
676}
677
678private function f_session_del(PFCP_session s) runs on CPF_ConnHdlr {
679 PFCP.send(ts_PFCP_Session_Del_Req(s.up_seid));
680 PFCP.receive(tr_PFCP_Session_Del_Resp(s.cp_seid));
681}
682
683private function f_tc_assoc(charstring id) runs on CPF_ConnHdlr {
684 f_assoc_setup();
685 f_assoc_release();
686 setverdict(pass);
687}
688
689/* Verify that the CPF can send a Node-ID of the IPv4 type */
690testcase TC_assoc_node_id_v4() runs on test_CT {
691 var CPF_ConnHdlr vc_conn;
692
693 f_init(guard_timeout := 5.0);
694 vc_conn := f_start_handler(refers(f_tc_assoc));
695 vc_conn.done;
696 f_shutdown_helper();
697}
698
699/* Verify that the CPF can send a Node-ID of the FQDN type */
700testcase TC_assoc_node_id_fqdn() runs on test_CT {
701 var CPF_ConnHdlr vc_conn;
702 var TestHdlrParams pars := f_gen_test_hdlr_pars();
703
704 pars.local_node_id := valueof(ts_PFCP_Node_ID_fqdn("\7example\3com"));
705
706 f_init(guard_timeout := 5.0);
707 vc_conn := f_start_handler(refers(f_tc_assoc), pars);
708 vc_conn.done;
709 f_shutdown_helper();
710}
711
712/* Verify PFCP Session Establishment and Deletion */
713private function f_tc_session_est(charstring id) runs on CPF_ConnHdlr {
714 f_assoc_setup();
715 var PFCP_session s := f_session_est_default();
716 f_sleep(1.0);
717 f_vty_expect_session_active(UPFVTY, s);
718 f_session_del(s);
719 f_vty_expect_no_active_sessions(UPFVTY);
720 f_vty_expect_no_gtp_actions(UPFVTY);
721 f_assoc_release();
722 setverdict(pass);
723}
724testcase TC_session_est() runs on test_CT {
725 var CPF_ConnHdlr vc_conn;
726
727 f_init(guard_timeout := 15.0);
728 vc_conn := f_start_handler(refers(f_tc_session_est));
729 vc_conn.done;
730 f_shutdown_helper();
731}
732
733/* Verify that releasing a PFCP Association also releases all its sessions and GTP actions. */
734private function f_tc_session_term_by_assoc_rel(charstring id) runs on CPF_ConnHdlr {
735 f_assoc_setup();
736 var PFCP_session s := f_session_est_default();
737 f_sleep(1.0);
738 f_vty_expect_session_active(UPFVTY, s);
739 f_assoc_release();
740 f_vty_expect_no_active_sessions(UPFVTY);
741 f_vty_expect_no_gtp_actions(UPFVTY);
742 setverdict(pass);
743}
744testcase TC_session_term_by_assoc_rel() runs on test_CT {
745 var CPF_ConnHdlr vc_conn;
746
747 f_init(guard_timeout := 15.0);
748 vc_conn := f_start_handler(refers(f_tc_session_term_by_assoc_rel));
749 vc_conn.done;
750 f_shutdown_helper();
751}
752
753/* Verify that PFCP Sessions with a src-interface other than ACCESS or CORE are ACKed by osmo-upf but have no effect. */
754private function f_tc_session_est_noop(charstring id) runs on CPF_ConnHdlr {
755 f_assoc_setup();
756 var PFCP_session s := f_create_PFCP_session();
757 f_session_est(s, f_ruleset_noop());
758
759 f_sleep(1.0);
760 f_vty_expect_session_status(UPFVTY, s, PFCP_session_inactive);
761
762 f_session_del(s);
763 f_vty_expect_no_active_sessions(UPFVTY);
764 f_vty_expect_no_gtp_actions(UPFVTY);
765 f_assoc_release();
766 setverdict(pass);
767}
768testcase TC_session_est_noop() runs on test_CT {
769 var CPF_ConnHdlr vc_conn;
770
771 f_init(guard_timeout := 15.0);
772 vc_conn := f_start_handler(refers(f_tc_session_est_noop));
773 vc_conn.done;
774 f_shutdown_helper();
775}
776
777control {
778 execute( TC_assoc_node_id_v4() );
779 execute( TC_assoc_node_id_fqdn() );
780 execute( TC_session_est() );
781 execute( TC_session_term_by_assoc_rel() );
782 execute( TC_session_est_noop() );
783}
784
785}